<?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=Jps112</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=Jps112"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Jps112"/>
	<updated>2026-05-25T09:48:08Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490536</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490536"/>
		<updated>2015-02-27T17:11:01Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the maximum values of the heat capacity, and the temperature this occurred at, for different lattice sizes: from this the Curie temperature could be found. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.2962&#039;&#039;&#039;. This compares quite well to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of &#039;&#039;&#039;2.269&#039;&#039;&#039;&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. This could be improved by using a higher order polynomial in order to more accurately find C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and therefore more accurately find C&amp;lt;sub&amp;gt;inf&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Reference==&lt;br /&gt;
&lt;br /&gt;
1. J. Kotze, Introduction to Monte Carlo methods for an Ising Model of a Ferromagnet, http://arxiv.org/pdf/0803.0217.pdf&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490515</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490515"/>
		<updated>2015-02-27T15:22:57Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.2962&#039;&#039;&#039;. This compares quite well to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of &#039;&#039;&#039;2.269&#039;&#039;&#039;&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. This could be improved by using a higher order polynomial in order to more accurately find C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and therefore more accurately find C&amp;lt;sub&amp;gt;inf&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Reference==&lt;br /&gt;
&lt;br /&gt;
1. J. Kotze, Introduction to Monte Carlo methods for an Ising Model of a Ferromagnet, http://arxiv.org/pdf/0803.0217.pdf&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490514</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490514"/>
		<updated>2015-02-27T15:22:34Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.2962&#039;&#039;&#039;. This compares quite well to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of &#039;&#039;&#039;2.269&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;. This could be improved by using a higher order polynomial in order to more accurately find C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and therefore more accurately find C&amp;lt;sub&amp;gt;inf&amp;lt;/sub&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Reference==&lt;br /&gt;
&lt;br /&gt;
1. J. Kotze, Introduction to Monte Carlo methods for an Ising Model of a Ferromagnet, http://arxiv.org/pdf/0803.0217.pdf&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490510</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490510"/>
		<updated>2015-02-27T15:07:42Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.2962&#039;&#039;&#039;. This compares quite well to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of 2.269&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. This could be improved by using a higher order polynomial in order to more accurately find C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and therefore more accurately find C&amp;lt;sub&amp;gt;inf&amp;lt;/sub&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490508</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490508"/>
		<updated>2015-02-27T14:48:16Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.30&#039;&#039;&#039;. This compares to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of &amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490507</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490507"/>
		<updated>2015-02-27T14:47:37Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 3rd Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 28JPS112.png|center|400px| Figure 29: Finding &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, this was done on Microsoft Excel for simplicity and this can be seen from figure 29. The equation of the trend-line of this data is also shown and gives the value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; to be &#039;&#039;&#039;2.30&#039;&#039;&#039;. This compares to a literature value of &amp;lt;math&amp;gt;T_{C, \inf}&amp;lt;/math&amp;gt; of &#039;&#039;&#039;&#039;&#039;&#039;&amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_28JPS112.png&amp;diff=490506</id>
		<title>File:Figure 28JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_28JPS112.png&amp;diff=490506"/>
		<updated>2015-02-27T14:47:28Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490505</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490505"/>
		<updated>2015-02-27T14:42:10Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
These results could be ploted using the equation&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490500</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490500"/>
		<updated>2015-02-27T14:19:45Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490499</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490499"/>
		<updated>2015-02-27T14:19:18Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Finding the peak in C */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 8: The Values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;!!C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt;&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||2.52252252||0.414795316278&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||2.45345345||0.816615090356&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||2.36276276||1.19327965652&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||2.31931932||1.56571895874&lt;br /&gt;
&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||2.29479479||1.83746564575&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The code was then altered in order to find the max values of the heat capacity and the temperature this occurred for, for different lattice sizes and from this the Curie temperature. The values of T&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; and C&amp;lt;sub&amp;gt;max&amp;lt;/sub&amp;gt; for the different size lattices can be seen on the right in table 8 (for 2x2, 4x4, and 8x8 a 3rd order polynomial was just, for a 16x16 a 5th order polynomial was used and for the 32x32 lattice a 7th order polynomial was used). The result from changing the code can be seen result below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 7)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        Cmax = np.max(fitted_C_values)&lt;br /&gt;
        Tmax = T_range[fitted_C_values == Cmax]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 2])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
        print (Cmax)&lt;br /&gt;
        print (Tmax)&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490496</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490496"/>
		<updated>2015-02-27T13:49:31Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Fitting in a particular temperature range */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 3rd order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490495</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490495"/>
		<updated>2015-02-27T13:48:59Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Fitting in a particular temperature range */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 425th order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS1121.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS1121.png&amp;diff=490494</id>
		<title>File:Figure 27JPS1121.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS1121.png&amp;diff=490494"/>
		<updated>2015-02-27T13:48:48Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490493</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490493"/>
		<updated>2015-02-27T13:47:33Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Fitting in a particular temperature range */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 425th order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.6&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        fit = np.polyfit(peak_T_values, peak_C_values, 3)&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS112.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS112.png&amp;diff=490492</id>
		<title>File:Figure 27JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS112.png&amp;diff=490492"/>
		<updated>2015-02-27T13:46:51Z</updated>

		<summary type="html">&lt;p&gt;Jps112: Jps112 uploaded a new version of &amp;amp;quot;File:Figure 27JPS112.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490192</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490192"/>
		<updated>2015-02-26T20:28:04Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Polynomial fitting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The result of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used to fit the data was a 425th order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 425) # fit a third order polynomial&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(peak_T_values, peak_C_values, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS112.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490191</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490191"/>
		<updated>2015-02-26T20:25:40Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Comparison of Python Data with C++ Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this is not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of an LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice; A is a constant which is not important. It is possible to compare a C++ program, that has run much longer simulations, to  the data computed using Python. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used it fit the data was a 425th order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 425) # fit a third order polynomial&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(peak_T_values, peak_C_values, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS112.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490129</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490129"/>
		<updated>2015-02-26T18:29:29Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Polynomial fitting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The polynomial used it fit the data was a 425th order one. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 425) # fit a third order polynomial&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(peak_T_values, peak_C_values, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 27JPS112.png|center|400px| Figure 28: Graph Showing the C++ Data and an 425th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS112.png&amp;diff=490119</id>
		<title>File:Figure 27JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_27JPS112.png&amp;diff=490119"/>
		<updated>2015-02-26T18:25:51Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490117</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490117"/>
		<updated>2015-02-26T18:24:17Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Fitting in a particular temperature range */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
This code could then be modified so that it only fitted the data in the region required. The change is shown below:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 425) # fit a third order polynomial&lt;br /&gt;
        T_min = 2.0&lt;br /&gt;
        T_max = 2.5&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        selection = np.logical_and(T &amp;gt; T_min, T &amp;lt; T_max) #choose only those rows where both conditions are true&lt;br /&gt;
        peak_T_values = T[selection]&lt;br /&gt;
        peak_C_values = C[selection]&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(peak_T_values, peak_C_values, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;], bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490112</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490112"/>
		<updated>2015-02-26T18:15:39Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Polynomial fitting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
====Fitting in a particular temperature range====&lt;br /&gt;
&lt;br /&gt;
====Finding the peak in C====&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490111</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490111"/>
		<updated>2015-02-26T18:14:57Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* The Effect of System Size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490110</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490110"/>
		<updated>2015-02-26T18:14:48Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Determining the Heat Capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490109</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490109"/>
		<updated>2015-02-26T18:14:35Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy. The reuslt of this can be seen below with a 11th order polynomial fit along with the code used.&lt;br /&gt;
&lt;br /&gt;
    def plot(file):&lt;br /&gt;
        data = np.loadtxt(file) #assume data is now a 2D array containing two columns, T and C&lt;br /&gt;
        T = data[:,0] #get the first column&lt;br /&gt;
        C = data[:,5] # get the last column&lt;br /&gt;
        fit = np.polyfit(T, C, 101) # fit a third order polynomial&lt;br /&gt;
        T_min = np.min(T)&lt;br /&gt;
        T_max = np.max(T)&lt;br /&gt;
        T_range = np.linspace(T_min, T_max, 1000) #generate 1000 evenly spaced points between T_min and T_max&lt;br /&gt;
        fitted_C_values = np.polyval(fit, T_range) # use the fit object to generate the corresponding values of C&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(T, C, T_range, fitted_C_values)&lt;br /&gt;
        pl.legend([&amp;quot;C++ data&amp;quot;, &amp;quot;Polynomial&amp;quot;])&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 26JPS112.png|center|400px| Figure 27: Graph Showing the C++ Data and an 11th Order Polynomial]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_26JPS112.png&amp;diff=490108</id>
		<title>File:Figure 26JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_26JPS112.png&amp;diff=490108"/>
		<updated>2015-02-26T18:14:27Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490099</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490099"/>
		<updated>2015-02-26T18:06:35Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Polynomial fitting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
In order to find where the heat capacity is at a maximum, the data will be fitted to a polynomial. This is done using the polyfit and polyval functions from NumPy.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490078</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490078"/>
		<updated>2015-02-26T17:36:53Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
===Comparison of Python Data with C++ Data===&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 25JPS112.png|right|400px| Figure 26: Graph Showing the Difference Between the Python and C++ Data]]&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed. The code used for this is shown below and the comparison for an 8x8 lattice is shown to the right:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def cap(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        cap = data[:,5]&lt;br /&gt;
        return cap&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.3])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), cap(b))&lt;br /&gt;
        pl.legend([&amp;quot;My data&amp;quot;, &amp;quot;C++ Data&amp;quot;])&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_25JPS112.png&amp;diff=490067</id>
		<title>File:Figure 25JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_25JPS112.png&amp;diff=490067"/>
		<updated>2015-02-26T17:30:05Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490032</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490032"/>
		<updated>2015-02-26T16:57:48Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Locating the Curie Temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
It is clear from the previous section that the  heat capacity becomes strongly peaked in the vicinity of the critical temperature around 2 to 2.5. The peak of the heat capacity became more sharp as the lattice size was increased. If there was an infinite size lattice then the critical temperature would diverge at the Curie temperature. Obviously this in not possible and in fact, not only does the heat capacity not diverge with different lattice sizes but the Curie temperature also changes. However the temperature at which the maximum heat capacity is found is modeled using the equation &amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C, \inf}&amp;lt;/math&amp;gt;, T&amp;lt;sub&amp;gt;C,L&amp;lt;/sub&amp;gt; is the Curie temperature of a LxL lattice and T&amp;lt;sub&amp;gt;C, inf&amp;lt;/sub&amp;gt; is the Curie temperature of an infinity large lattice. A is a constant which is no important. It is possible to compare a C++ program, that has run much longer simulations, to  the data that I computed.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490017</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490017"/>
		<updated>2015-02-26T16:34:44Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Conclusion */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490016</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490016"/>
		<updated>2015-02-26T16:34:35Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Determining the Heat Capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112.png|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490015</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490015"/>
		<updated>2015-02-26T16:34:03Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.2])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
The result of this code can be seen below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 24JPS112|300px|thumb|center|Figure 25: The Heat Capacity versus Temperature]]&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_24JPS112.png&amp;diff=490014</id>
		<title>File:Figure 24JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_24JPS112.png&amp;diff=490014"/>
		<updated>2015-02-26T16:32:14Z</updated>

		<summary type="html">&lt;p&gt;Jps112: Jps112 uploaded a new version of &amp;amp;quot;File:Figure 24JPS112.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_24JPS112.png&amp;diff=490012</id>
		<title>File:Figure 24JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_24JPS112.png&amp;diff=490012"/>
		<updated>2015-02-26T16:31:12Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490010</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=490010"/>
		<updated>2015-02-26T16:30:50Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Determining the Heat Capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def temp2(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        temp2 = temp*temp&lt;br /&gt;
        return temp2&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy = data[:,1]&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def energy2(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energy2 = data[:,2]&lt;br /&gt;
        return energy2&lt;br /&gt;
    &lt;br /&gt;
    def var(file):&lt;br /&gt;
        var = energy2(file) - (energy(file)*energy(file))&lt;br /&gt;
        return var&lt;br /&gt;
    &lt;br /&gt;
    def heat(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        heat = (var(file)/temp2(file))/num&lt;br /&gt;
        return heat&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        pl.ylabel(&amp;quot;Heat Capacity&amp;quot;)&lt;br /&gt;
        pl.xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        pl.ylim([0, 1.5])&lt;br /&gt;
        pl.plot(temp(a), heat(a),temp(b), heat(b),temp(c), heat(c),temp(d), heat(d),temp(e), heat(e))&lt;br /&gt;
        pl.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        pl.legend()&lt;br /&gt;
        pl.show()&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489999</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489999"/>
		<updated>2015-02-26T16:08:32Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Determining the Heat Capacity */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
Increasing the temperature above the Curie temperature induces a phase transition. This means that the magnetisation of the system will rapidly drop and from this the heat capacity of the system can be found using the relationship &amp;lt;math&amp;gt;C = \frac{\partial E}{\partial T} = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;. It is known that the heat capacity should become very strongly peaked at the phase transition temperature and the code used to plot a graph showing the heat capacity versus temperature for each of lattice size is:&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489993</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489993"/>
		<updated>2015-02-26T15:54:23Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* The Effect of System Size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|right|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489992</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489992"/>
		<updated>2015-02-26T15:53:54Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[File:OptisingJPS1121.png|center|400px|]]!![[File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 16: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 17: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 18: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 19: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 20: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 21: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 22: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 23: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 23JPS112.png|center|400px| Figure 24: The Graph Showing the Energies and Magnetisations with Different Lattice Sizes]]&lt;br /&gt;
&lt;br /&gt;
The graph made by this code is shown on figure 24, to the right and it is clear that an 8x8 lattice is the minimum size necessary to observe the long term fluctuations. From the energy part of the graph the energies are almost the same from an 8x8 lattice and larger and so it is pointless to compute for a larger lattice  as it will not improve the results.&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_23JPS112.png&amp;diff=489991</id>
		<title>File:Figure 23JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_23JPS112.png&amp;diff=489991"/>
		<updated>2015-02-26T15:53:48Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489975</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489975"/>
		<updated>2015-02-26T15:42:46Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* The Effect of System Size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 12: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 14: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 15: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 16: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
From the previous section the energies and spins of different lattice sizes at different temperatures were found so that the onset of phase transition could be seen. It is possible to show all the data sets on the same graph and by doing this the minimum lattice size needed to accurately model the long range fluctuations that occur within the system. The code used in order to do this is shown below:&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489965</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489965"/>
		<updated>2015-02-26T15:36:00Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* The Effect of System Size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 12: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 14: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 15: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 16: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
    from math import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    &lt;br /&gt;
    def temp(file):&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        temp = data[:,0]&lt;br /&gt;
        return temp&lt;br /&gt;
    &lt;br /&gt;
    def energy(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        energ = data[:,1]&lt;br /&gt;
        energy = energ/num&lt;br /&gt;
        return energy&lt;br /&gt;
    &lt;br /&gt;
    def magn(file):&lt;br /&gt;
        filenum = int(file.split(&#039;x&#039;)[0])&lt;br /&gt;
        num = filenum*filenum&lt;br /&gt;
        data = np.loadtxt(file)&lt;br /&gt;
        mag = data[:,3]&lt;br /&gt;
        magn = mag/num&lt;br /&gt;
        return magn&lt;br /&gt;
    &lt;br /&gt;
    def plot(a,b,c,d,e):&lt;br /&gt;
        fig = pl.figure()&lt;br /&gt;
        enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
        enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
        enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        enerax.set_ylim([-2.1, 0])&lt;br /&gt;
        magax = fig.add_subplot(2,1,2)&lt;br /&gt;
        magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
        magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
        magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
        enerax.plot(temp(a), energy(a),temp(b), energy(b),temp(c), energy(c),temp(d), energy(d),temp(e), energy(e))&lt;br /&gt;
        enerax.legend([&amp;quot;2x2&amp;quot;, &amp;quot;4x4&amp;quot;, &amp;quot;8x8&amp;quot;, &amp;quot;16x16&amp;quot;, &amp;quot;32x32&amp;quot;],bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=5, mode=&amp;quot;expand&amp;quot;, borderaxespad=0.)&lt;br /&gt;
        magax.plot(temp(a), magn(a),temp(b), magn(b),temp(c), magn(c),temp(d), magn(d),temp(e), magn(e))&lt;br /&gt;
        pl.legend()&lt;br /&gt;
    pl.show()&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489920</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489920"/>
		<updated>2015-02-26T13:40:09Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Running Over a Range of Temperatures */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 12: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 14: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 15: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 16: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 8: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489919</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489919"/>
		<updated>2015-02-26T13:39:48Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added. In table 7. it is possible to see the number of ignored Monte Carlo steps for different size lattice.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Number of Monte Carlo Steps Ignored for Different Lattice Sizes&lt;br /&gt;
!Lattice Size!!Monte Carlo Steps Ignored!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2x2||0||[[File:Figure 20JPS112.png|center|400px| Figure 12: 2x2 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||100||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1000||[[File:Figure 9JPS112.png|center|400px| Figure 14: 8x8 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|16x16||5000||[[File:Figure 21JPS112.png|center|400px| Figure 15: 16x16 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|32x32||100000||[[File:Figure 22JPS112.png|center|400px| Figure 16: 32x32 Lattice]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_22JPS112.png&amp;diff=489918</id>
		<title>File:Figure 22JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_22JPS112.png&amp;diff=489918"/>
		<updated>2015-02-26T13:38:15Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_21JPS112.png&amp;diff=489917</id>
		<title>File:Figure 21JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_21JPS112.png&amp;diff=489917"/>
		<updated>2015-02-26T13:37:17Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_20JPS112.png&amp;diff=489916</id>
		<title>File:Figure 20JPS112.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Figure_20JPS112.png&amp;diff=489916"/>
		<updated>2015-02-26T13:36:52Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489586</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489586"/>
		<updated>2015-02-24T22:49:38Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Running Over a Range of Temperatures */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added.&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Change in Energy and Magnetisation of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489585</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489585"/>
		<updated>2015-02-24T22:48:39Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimised varied depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimised being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are examined: at a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed, indeed has not been observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed was also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than to underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000: therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations - a new variable was made n_ignore, which was the number of Monte Carlo steps that were to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetisation were not added to. The second change was that the average energy, energy squared, magnetisation and magnetisation squared were altered so that they only averaged for the number of counts that they had had added.&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Change in Energy and Magnetization of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489579</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489579"/>
		<updated>2015-02-24T22:41:08Z</updated>

		<summary type="html">&lt;p&gt;Jps112: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimensions and the Number of Neighbours&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimisation due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilisation energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetisation is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetisation is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetisation will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetisation again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Although both of the systems in figure 1 are not very magnetised, the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetised. As the system is at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetisation will be equal to the number of lattice cells, in this case 1000. Therefore the magnetisation is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetisation while the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetisation is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetisation of these lattices. One configuration corresponded to the energy minimum, one to the energy maximum, and one to an random intermediate state: this was found to work as expected. The result of this and a checkpoint, while that did not work as expected, are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. As this number is too large to be meaningful it has been converted to years, and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years to analyse all the configurations, longer than the age of the universe!&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetisation&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py; this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output, can be seen opposite. In one case all the spins in the system became +1, while in the other case the spins in the system became -1. As has been stated earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetisation.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it was necessary to check how quick the original code was, as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py, which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged, as each time it ran there were slightly different timings. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetisation was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimised Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code would take to perform 2000 Monte Carlo steps. As can be seen below, the new code was much faster than the original, &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error was also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code worked more quickly than the original and was more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behaviour of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. As it takes time for the system to reach the equilibrium state this will affect the outcome of any experiment taking place, so it will be necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached; as has been seen earlier, the energy sharply decreased before the minimum energy was reached. The energy and magnetisation should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on them using the file Il.finalframe.py and the number of steps required to reach equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to reach the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimized varies depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimized being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are looked at. At a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed. Indeed has not be observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed is also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000. Therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations. A new variable was made n_ignore, which was the number of Monte Carlo steps that were going to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetization were not added to. The second change was that the average energy, energy squared, magnetization and magnetization squared were altered so that they only averaged for the number of counts that they had had added.&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Change in Energy and Magnetization of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489572</id>
		<title>Rep:Mod:JPS1124</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:JPS1124&amp;diff=489572"/>
		<updated>2015-02-24T22:13:20Z</updated>

		<summary type="html">&lt;p&gt;Jps112: /* Calculating the Energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Third Year CMP Compulsory Experiment&#039;&#039;&#039; James Simpson (CID:00733493)&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Ising Model==&lt;br /&gt;
&lt;br /&gt;
===The Model===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 1: The Relationship Between the Number of Dimension and the Number of Neighbors&lt;br /&gt;
!Number of Dimensions!!Number of Neighbors&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|1||2&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|2||4&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|3||6&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|D||2D&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The Ising model is an physics model used in order to understand the behaviour of ferromagnets. Ferromagnets are materials in which the magnetic dipoles of the material align so that an overall magnetic dipole is exhibited by the material. This effect is due to the favourable energy minimization due to the alignment of the dipoles or spins. However this will unfavourably decrease the entropy. In the model only spins between neighbouring lattice points interact. This interaction is defined as &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;, where J is a constant and s&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt; and s&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt; are the spins of the lattice point and its neighbour. The third rule is that a cell at the edge of the lattice will interact with another cell at the other edge of the lattice; this is so that all possible neighbours will be interacted with. The number of neighbours in a particular number of dimensions is expressed in table 1.&lt;br /&gt;
&lt;br /&gt;
The interaction energy of a number of particles in a particular number of dimensions can be expressed as &amp;lt;math&amp;gt;E=-DNJ&amp;lt;/math&amp;gt;. This can be shown using the initial equation in the lab script of &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;. From table 1 it is clear that the number of neighbours each lattice cell has is equal to twice the number of dimensions. The first step is to show what the lowest interaction energy must be; in the lowest energy all the magnetic spins are parallel and so &amp;lt;math&amp;gt;s_i s_j=1&amp;lt;/math&amp;gt; this means that the &amp;lt;math&amp;gt; \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j&amp;lt;/math&amp;gt; term in the equation will simply be equal to the number of neighbours which is 2-D. This is simply done N times as there as N number of particles and so &amp;lt;math&amp;gt; \sum_i^N \sum_{j\ \in\ \mathrm{neighbours}\left(i\right)}s_i s_j=2ND&amp;lt;/math&amp;gt;. This is then multiplied by the constant of &amp;lt;math&amp;gt;- \frac{1}{2} J&amp;lt;/math&amp;gt;, giving the interaction energy to be &amp;lt;math&amp;gt;-DNJ&amp;lt;/math&amp;gt;. From this point it is possible to consider the multiplicity of the system. Ordinarily in chemistry the multiplicity of a system is given by the equation &amp;lt;math&amp;gt;\mathrm{Multiplicity}=2s+1&amp;lt;/math&amp;gt;, but this cannot be done in this case as the values of the magnetic spin are integers and so the lattice cells can be considered as quasi-boson particles. As the equation stated in the previous sentence is designed for electrons it must be ignored, as in the case of a one-lattice cell unit it would give a multiplicity of 3 where a value of 2 is logically expected.  All the magnetic spins in a ferromagnetic material will be aligned so that the spins are all parallel, however the spins of each lattice cell can have a value of +1 or a value of -1. This means that the number of micro-states is 2 and so the multiplicity is &#039;&#039;&#039;2&#039;&#039;&#039;. It then follows that the entropy of the system given by &amp;lt;math&amp;gt;S=k_b \ln(\Omega)&amp;lt;/math&amp;gt;, where Ω is the number of micro-states, in this case the multiplicity of the system. Therefore the entropy of the system is simply &amp;lt;math&amp;gt;S=k_b \ln(2)&amp;lt;/math&amp;gt; which is &#039;&#039;&#039;9.570x10&amp;lt;sup&amp;gt;-24&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Phase Transition===&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration the interaction energy is -3000J. However, when one spin changes the interaction energy of the system will naturally increase. In order to consider how much the energy increases when a spin flips, the effect this will have on the system must be thought about. It is equivalent to removing the spin entirely and putting back into the system another spin, but with a direction opposite to the rest of the system. When a spin is removed the system will lose six interactions, one for each neighbour, and then when the opposite spin is added there will be six interactions created: however this will increase the interaction energy by 6J. So, the flipping of the spin destabilizes the energy by &#039;&#039;&#039;12J&#039;&#039;&#039; in total as 6J of favourable interactions are lost and 6J of unfavourable interactions are gained. This leads to the interaction energy of this system being -2988J. The new entropy of the system will be &amp;lt;math&amp;gt;S=k_b \ln(2000)&amp;lt;/math&amp;gt; as the new spin can occur anywhere in the lattice and there are 1000 options for that, and in addition there will be 2 options for every point in the lattice where the opposite spin will be. This is because the majority of the system can a have a spin of +1, where the opposite spin will be -1. The other option is where most of the system will have a spin of -1 and in this case the opposite spin will have a value of +1. This means when compared to the lowest energy configuration the entropy will have increased by &amp;lt;math&amp;gt;S=k_b \ln(1000)&amp;lt;/math&amp;gt;, which is &#039;&#039;&#039;9.54x10&amp;lt;sup&amp;gt;-23&amp;lt;/sup&amp;gt; JK&amp;lt;sup&amp;gt;-1&amp;lt;/sup&amp;gt;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|300px|thumb|right|Figure 1: The 1-D and 2-D Lattices Given in the Lab Script used in this Exercise]]&lt;br /&gt;
&lt;br /&gt;
The Curie temperature is the temperature below which ferromagnetism will be exhibited. At temperatures below the Curie temperature the stabilization energy will be large enough to compensate for the loss in entropy. However above the Curie temperature this is not the case and the entropy effect will dominate, leading to the material showing diamagnetism. Magnetization is given as &amp;lt;math&amp;gt;M=\sum_i s_i&amp;lt;/math&amp;gt; and so in all cases the magnetization is simply the difference between the number of +1 spins and the number of -1 spins. In the case of the 1-D lattice there are three +1 spins and two -1 spins and so the magnetization will be simply &#039;&#039;&#039;+1&#039;&#039;&#039;. For the 2-D case there are thirteen +1 spins and twelve -1 spins, meaning that the magnetization again in that case is &#039;&#039;&#039;+1&#039;&#039;&#039;. Both of the systems in figure 1 are not very magnetized, however the Ising lattice in 3-D containing 1000 lattice cells at absolute zero will be highly magnetized. As we are at absolute zero there will be no thermal energy available in order to overcome the spin flipping energy barriers, therefore it is expected that the system will adopt the lowest energy configuration, which is where all the spins align. This means that the value of the magnetization will be equal to the number of lattice cells, in this case 1000. Therefore the magnetization is either &#039;&#039;&#039;-1000 or +1000&#039;&#039;&#039;, but it cannot be known which of these two options is correct without further investigation.&lt;br /&gt;
&lt;br /&gt;
==Calculating the Energy and Magnetisation==&lt;br /&gt;
&lt;br /&gt;
Whenever the ipython programme was loaded the following two lines were run in order to start the session %load_ext autoreload and %autoreload 2. Firstly the files IsingLattice.py and ILcheck.py were extracted and stored in the H:Drive.&lt;br /&gt;
&lt;br /&gt;
===Modifying the Files===&lt;br /&gt;
&lt;br /&gt;
This section involved two parts: one of these was to find the magnetization will the other was to find the energy of a random arrangement of a specific lattice size, determined by the user. The code used to find the magnetization is shown below:&lt;br /&gt;
        &lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        magnetisation = 0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
                magnetisation += self.lattice [i,j]&lt;br /&gt;
        #Return the total magnetisation of the current lattice configuration.&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The code used to determine the energy of the lattice is shown below:&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        ener = 0.0&lt;br /&gt;
        for i in range(self.n_rows):&lt;br /&gt;
            for j in range (self.n_cols):&lt;br /&gt;
            # 1st row, 1st column corner&lt;br /&gt;
                if j == 0:&lt;br /&gt;
            # Last row, 1st column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of 1st column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # 1st row, last column corner&lt;br /&gt;
                elif j == self.n_cols-1:&lt;br /&gt;
            # Last row, last column corner&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,0]&lt;br /&gt;
                else:&lt;br /&gt;
            # Rest of last column&lt;br /&gt;
                    if i == self.n_rows-1:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [0,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
            # Rest of lattice&lt;br /&gt;
                    else:&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i+1,j]&lt;br /&gt;
                        ener += self.lattice [i,j]*self.lattice [i,j+1]&lt;br /&gt;
        energy = ener*-1&lt;br /&gt;
        &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
===Testing the Code===&lt;br /&gt;
&lt;br /&gt;
This code was then tested using the file Ilcheck.py which created three lattices and checked the energy and magnetization of these lattices. One configuration corresponds to the energy minimum, one to the energy maximum, and one to an random intermediate state, this was found to work as expected. The result of this and a checkpoint while that didn&#039;t work as expected are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatcentre&amp;quot;&lt;br /&gt;
|+Table 2: The Correct and an Incorrect Checkpoint Files&lt;br /&gt;
![[ File:OptisingJPS1121.png|center|400px|]]!![[ File:OptisingJPS1122.png|center|400px|]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Figure 2: A Incorrect Checkpoint File||Figure 3: The Correct Checkpoint File&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Introduction to the Monte Carlo simulation==&lt;br /&gt;
&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
&lt;br /&gt;
A system that contains 100 lattice cells with each lattice cell being allowed to be one of two states, spin up or spin down, has a certain number of states available to it. Using the equation for the number of micro-states &amp;lt;math&amp;gt;\Omega = n^N&amp;lt;/math&amp;gt;, where n is the number of energy levels available and N is the number of particles, it is found that the number of micro-states will be &amp;lt;math&amp;gt;\Omega = 2^{100}&amp;lt;/math&amp;gt; or &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;30&amp;lt;/sup&amp;gt;&#039;&#039;&#039; states. If the computer can analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations in a second then it would take &#039;&#039;&#039;1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt;&#039;&#039;&#039; seconds to analyse all the configurations. This number is too large to be meaningful so it has been converted to years and in years it would take &#039;&#039;&#039;4.02x10&amp;lt;sup&amp;gt;13&amp;lt;sup&amp;gt;&#039;&#039;&#039; years in analyse all the configurations, longer than the age of the universe.&lt;br /&gt;
&lt;br /&gt;
===Importance Sampling.===&lt;br /&gt;
&lt;br /&gt;
The code used for the Monte Carlo simulation is shown below:&lt;br /&gt;
&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        # complete this function so that it performs a single Monte Carlo step&lt;br /&gt;
        energy = self.energy()&lt;br /&gt;
        magnetisation = self.magnetisation()&lt;br /&gt;
        #the following two lines will select the coordinates of the random spin for you&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        #the following line will choose a random number in the rang e[0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        # Randomly change a spin&lt;br /&gt;
        if self.lattice [random_i, random_j] == 1:&lt;br /&gt;
        # From 1 go to -1&lt;br /&gt;
            self.lattice [random_i, random_j] = -1&lt;br /&gt;
        else:&lt;br /&gt;
        # From -1 go to 1&lt;br /&gt;
            self.lattice [random_i, random_j] = 1&lt;br /&gt;
        energy1 = self.energy()&lt;br /&gt;
        magnetisation1 = self.magnetisation()&lt;br /&gt;
        deltaenergy = energy1 - energy&lt;br /&gt;
        if deltaenergy &amp;lt; 0:&lt;br /&gt;
        # Energy goes down&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        elif random_number &amp;lt;= exp(- deltaenergy/ T):&lt;br /&gt;
        # Energy goes up but smaller than random number&lt;br /&gt;
            energy = energy1&lt;br /&gt;
            magnetisation = magnetisation1&lt;br /&gt;
        else:&lt;br /&gt;
        # Energy goes up and larger than random number&lt;br /&gt;
            self.lattice [random_i, random_j] = - self.lattice [random_i, random_j]&lt;br /&gt;
        self.E += energy&lt;br /&gt;
        self.E2 += energy**2&lt;br /&gt;
        self.M += magnetisation&lt;br /&gt;
        self.M2 += magnetisation**2&lt;br /&gt;
        self.n_cycles += 1&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/self.n_cycles&lt;br /&gt;
        aveE2 = self.E2/self.n_cycles&lt;br /&gt;
        aveM = self.M/self.n_cycles&lt;br /&gt;
        aveM2 = self.M2/self.n_cycles&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable floatright&amp;quot;&lt;br /&gt;
|+Table 3: Monte Carlo Simulation Results&lt;br /&gt;
![[File:Figure 5JPS112.png|300px|thumb|right|Figure 4: Example 1 of the Minimum energy]]!![[File:Figure 6jps112.png|300px|thumb|right|Figure 5: Example 2 of the Minimum energy]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy||-1.47164536741||-1.4658836689&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetization||-0.616646698616|| 0.606508668904&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Energy&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||2.34781017039||2.34409081376&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|Average Magnetization&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;||0.475055536142||0.471877403174&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The code was then tested using the file ILanim.py this ran a Monte Carlo simulation of an eight by eight lattice and displayed the output at a temperature of 1 temperature unit. The results of two runs of this testing, including the display output can be seen opposite. In one case all the spins in the system become +1, while in the other case the spins in the system become -1. As has been states earlier, below the Curie temperature the lattice will be ferromagnetic and so the spins will align. This means that it is expected that there will be spontaneous magnetization.&lt;br /&gt;
&lt;br /&gt;
==Accelerating the Code==&lt;br /&gt;
&lt;br /&gt;
In order to check if the code has been accelerated it is necessary to check how quick the original code is as an accelerated code must be quicker. The speed of the original code was tested using the file ILtimetrial.py which ran 2000 steps of the Monte Carlo simulation. This was done 5 times and then averaged as it took slightly different times each time. The times of each run and the average can be seen from table 4 below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 4: The Time Taken, in Seconds, for the Original Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|6.5321323358111965||6.5576305262353145||6.548430656233478||6.566359750713659||6.561720323517164||6.553254719&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The standard error of the sample was found to be 0.006049722. Using the numpy.sum function, a new code for the magnetization was made. This code is shown below:&lt;br /&gt;
&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
        magnetisation = np.sum(self.lattice)&lt;br /&gt;
        return magnetisation&lt;br /&gt;
&lt;br /&gt;
The new code for determining the energy is show below. This was done using the numpy multiply and numpy roll functions.&lt;br /&gt;
&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=0))&lt;br /&gt;
        np.multiply(self.lattice, np.roll(self.lattice,1,axis=1))&lt;br /&gt;
        return energy&lt;br /&gt;
&lt;br /&gt;
The code still gave the expected result when tested using the file ILcheck.py, this can be seen to the below:&lt;br /&gt;
&lt;br /&gt;
[[File:Figure 7JPS112.png|center|400px| Figure 6: The Checkpoint File of the Re-optimized Code]]&lt;br /&gt;
&lt;br /&gt;
The file ILtimetrial was used again in order to find how long the new code takes to perform 2000 Monte Carlo steps. As can be seen below the new code is much faster that the original &#039;&#039;&#039;0.387094820036961&#039;&#039;&#039; seconds compared to &#039;&#039;&#039;6.553254719&#039;&#039;&#039; seconds. The standard error is also decreased, &#039;&#039;&#039;0.000177801&#039;&#039;&#039; compared to &#039;&#039;&#039;0.006049722&#039;&#039;&#039;. This means that the new code works more quickly than the original and is more consistent in the time taken to perform 2000 Monte Carlo steps.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 5: The Time Taken, in Seconds, for the Accelerated Code to Perform 2000 Monte Carlo Steps&lt;br /&gt;
!Attempt 1!!Attempt 2!!Attempt 3!!Attempt 4!!Attempt 5!!Average Time Taken&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|0.384887314998096||0.38467463684389713||0.3845507255513354||0.3855771603227396||0.3848634022924955||0.387094820036961&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of Temperature==&lt;br /&gt;
&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
&lt;br /&gt;
The behavior of the lattice using the Ising model can now be tested in order to probe further into the Curie temperature, the area where the change of domination between the enthalpic and entropic terms takes place. It takes time for the system to reach the equilibrium state and this will effect the outcome of any experiment taking place, so it is necessary to ignore the first few Monte Carlo steps until the equilibrium state is reached, this can be seen earlier that the energy sharply decreases before the minimum energy is reached. The energy and magnetization should only be averaged after equilibrium has been reached. Lattices of different sizes and different temperature had 150000 Monte Carlo steps performed on it using the file Il.finalframe.py and the number of steps required to each equilibrium are shown below.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 6: The Number of Monte Carlo Steps Needed for the Lattice to each the Minimum Energy&lt;br /&gt;
!Lattice Size!!Temperature!!Monte Carlo Steps Needed!!Picture&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||656||[[File:Figure 8JPS112.png|center|400px| Figure 7: 8x8 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||459||[[File:Figure 9JPS112.png|center|400px| Figure 8: 8x8 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||478||[[File:Figure 10JPS112.png|center|400px| Figure 9: 8x8 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1||571||[[File:Figure 11JPS112.png|center|400px| Figure 10: 8x8 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||1.5||-||[[File:Figure 12JPS112.png|center|400px| Figure 11: 8x8 Lattice, T=1.5]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||19||[[File:Figure 13JPS112.png|center|400px| Figure 12: 4x4 Lattice, T=1, Run 1]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||59||[[File:Figure 14JPS112.png|center|400px| Figure 13: 4x4 Lattice, T=1, Run 2]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 15JPS112.png|center|400px| Figure 14: 4x4 Lattice, T=1, Run 3]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1||47||[[File:Figure 16JPS112.png|center|400px| Figure 15: 4x4 Lattice, T=1, Run 4]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|4x4||1.5||-||[[File:Figure_17JPS112.png|center|400px| Figure 11: 4x4 Lattice, T=1.5]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
As can be seen from table 6, the amount of time required for the energy to be minimized varies depending on a number of factors, the temperature of the system and the size of the lattice. Decreasing the number of lattice points from an 8x8 lattice to a 4x4 lattice, a decrease of 75%, resulted, on average, in the number of Monte Carlo steps needed for the energy to be minimized being decreased by a factor of 10. The number of Monte Carlo steps needed when the temperature was increased from 1 to 1.5 is not shown. The reason for this is clear if the diagrams within table 6 are looked at. At a higher temperature more high level energy levels will be populated and so the minimum energy will be less easily observed. Indeed has not be observed in either the 8x8 or 4x4 lattice at 1.5. The number of Monte Carlo steps needed is also different in each run as can be seen from table 6. It is better to overestimate the number of steps that will be required than underestimate. All the values of Monte Carlo steps required for a 4x4 lattice were less than 100 and for a 8x8 lattice they were all less than 1000. Therefore the first 100 steps should be ignored for a 4x4 lattice and the first 1000 steps should be ignored for an 8x8 lattice. The file ILfinalframe.py was then modified with the following addition and alterations. A new variable was made n_ignore, which was the number of Monte Carlo steps that were going to be ignored.&lt;br /&gt;
&lt;br /&gt;
        if self.n_cycles &amp;gt;= self.n_ignore:&lt;br /&gt;
            self.E += energy&lt;br /&gt;
            self.E2 += energy**2&lt;br /&gt;
            self.M += magnetisation&lt;br /&gt;
            self.M2 += magnetisation**2&lt;br /&gt;
            self.n_cycles += 1&lt;br /&gt;
        else:&lt;br /&gt;
            pass&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
   &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles&lt;br /&gt;
&lt;br /&gt;
This code was altered so that, if the count was below the number of Monte Carlo steps needed before the lattice that reached the minimum energy, then the energy and magnetization were not added to. The second change was that the average energy, energy squared, magnetization and magnetization squared were altered so that they only averaged for the number of counts that they had had added.&lt;br /&gt;
&lt;br /&gt;
===Running Over a Range of Temperatures===&lt;br /&gt;
&lt;br /&gt;
The following code was changed in the file IsingLattice.py and the empty lists El and Ml were made:&lt;br /&gt;
&lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        # complete this function so that it calculates the correct values for the averages of E, E*E (E2), M, M*M (M2), and returns them&lt;br /&gt;
        aveE = self.E/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveE2 = self.E2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM = self.M/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        aveM2 = self.M2/(self.n_cycles-self.n_ignore)&lt;br /&gt;
        sdeve = np.std(self.El)&lt;br /&gt;
        sdevm = np.std(self.Ml)&lt;br /&gt;
        serre = sdeve / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        serrm = sdevm / sqrt(self.n_cycles-self.n_ignore)&lt;br /&gt;
        return aveE, aveE2, aveM, aveM2, self.n_cycles, serre, serrm&lt;br /&gt;
&lt;br /&gt;
From the file ILtemperaturerange.py the code was changed to:&lt;br /&gt;
&lt;br /&gt;
    from IsingLattice import *&lt;br /&gt;
    from matplotlib import pylab as pl&lt;br /&gt;
    import numpy as np&lt;br /&gt;
    n_rows = 4&lt;br /&gt;
    n_cols = 4&lt;br /&gt;
    n_ignore = 100&lt;br /&gt;
    il = IsingLattice(n_rows, n_cols)&lt;br /&gt;
    il.lattice = np.ones((n_rows, n_cols))&lt;br /&gt;
    spins = n_rows*n_cols&lt;br /&gt;
    runtime = 10000&lt;br /&gt;
    times = range(runtime)&lt;br /&gt;
    temps = np.arange(0.25, 5.0, 0.25)&lt;br /&gt;
    energies = []&lt;br /&gt;
    magnetisations = []&lt;br /&gt;
    energysq = []&lt;br /&gt;
    magnetisationsq = []&lt;br /&gt;
    serrma = []&lt;br /&gt;
    serren = []&lt;br /&gt;
    for t in temps:&lt;br /&gt;
        for i in times:&lt;br /&gt;
            if i % 100 == 0:&lt;br /&gt;
                print(t, i)&lt;br /&gt;
            energy, magnetisation = il.montecarlostep(t)&lt;br /&gt;
        aveE, aveE2, aveM, aveM2, n_cycles, serre, serrm = il.statistics()&lt;br /&gt;
        energies.append(aveE)&lt;br /&gt;
        energysq.append(aveE2)&lt;br /&gt;
        magnetisations.append(aveM)&lt;br /&gt;
        magnetisationsq.append(aveM2)&lt;br /&gt;
        serrma.append(serrm)&lt;br /&gt;
        serren.append(serre)&lt;br /&gt;
        #reset the IL object for the next cycle&lt;br /&gt;
        il.E = 0.0&lt;br /&gt;
        il.E2 = 0.0&lt;br /&gt;
        il.M = 0.0&lt;br /&gt;
        il.M2 = 0.0&lt;br /&gt;
        il.n_cycles = 0&lt;br /&gt;
    fig = pl.figure()&lt;br /&gt;
    enerax = fig.add_subplot(2,1,1)&lt;br /&gt;
    enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
    enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    enerax.errorbar(temps, np.array(energies)/spins, xerr=0, yerr= np.array(serren)/spins)&lt;br /&gt;
    enerax.set_ylim([-2.1, 2.1])&lt;br /&gt;
    magax = fig.add_subplot(2,1,2)&lt;br /&gt;
    magax.set_ylabel(&amp;quot;Magnetisation per spin&amp;quot;)&lt;br /&gt;
    magax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
    magax.errorbar(temps, np.array(magnetisations)/spins, xerr=0, yerr= np.array(serrma)/spins)&lt;br /&gt;
    magax.set_ylim([-1.1, 1.1])&lt;br /&gt;
    enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
    magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
    pl.show()&lt;br /&gt;
    final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
    np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Table 7: The Change in Energy and Magnetization of Lattices with Temperature&lt;br /&gt;
![[File:Figure 18JPS112.png|center|400px| Figure 18: 8x8 Lattice]]!![[File:Figure 19JPS112.png|center|400px| Figure 19: 4x4 Lattice]]&lt;br /&gt;
|- style=&amp;quot;text-align:center;&amp;quot;&lt;br /&gt;
|8x8||4x4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==The Effect of System Size==&lt;br /&gt;
&lt;br /&gt;
==Determining the Heat Capacity==&lt;br /&gt;
&lt;br /&gt;
==Locating the Curie Temperature==&lt;br /&gt;
&lt;br /&gt;
==Conclusion==&lt;br /&gt;
&lt;br /&gt;
==References==&lt;/div&gt;</summary>
		<author><name>Jps112</name></author>
	</entry>
</feed>