<?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=Rm1412</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=Rm1412"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Rm1412"/>
	<updated>2026-04-04T02:41:56Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487625</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487625"/>
		<updated>2015-02-16T05:47:47Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: K/* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin fluctuates over a wider temperature range than the average magnetisation/spin, which changes more abruptly during a phase transition. Furthermore, the energy/spin of the 2x2, 4x4 and 8x8 converge to ca. 0 energy/spin over a smaller temperature range than the energy/spin of the 16x16 and 32x32 lattices. Therefore, for the simulation to properly model long range fluctuations in the lattice during the phase transition the minimum lattice size should be 16x16.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots show that the polynomial degree required to obtain a good fit to the heat capacity/spin vs temperature curve increases with increasing lattice size. np.polyfit() issues the following RankWarning when the polynomial degree is above 8, indicating that the solution is &amp;quot;ill conditioned&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
C:\Users\rm1412\AppData\Local\Continuum\Anaconda\lib\site-packages\numpy\lib\polynomial.py:588: RankWarning: Polyfit may be poorly conditioned&lt;br /&gt;
  warnings.warn(msg, RankWarning)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Therefore, in later parts of this experiment, curves will only be fitted to polynomials with degrees in the range 2-8.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Plots of heat capacity/spin vs temperature show that the peak in heat capacity/spin occurs in the temperature range 2 K - 3 K for all lattice size. Therefore, the heat capacity/spin vs temperature curves will only be fitted within the range of 2 K to 3 K.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#This function fits a polynomial of degree 8 to C++ simulation heat capacity/spin data, then finds the Curie temperature for each lattice size and writes them #along with the lattice side lengths to a file to be read by ILCurieplot.py&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[] #this list stores the lattice side lengths&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = [] #this list stores the values of the Curie temperatures of lattices with different sizes&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    #the following lines fit a polynomial of degree 8 to C++ simulation heat capacity/spin data in the region 2 K-3 K&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    #the following lines find the Curie temperature, which corresponds to the maximum of the heat capacity/spin vs temperature curve&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
#This function reads data from the file produced by ILTcextract.py, fits the Curie temperature data to a straight line, then plots the fitted line and the Curie #temperature data vs 1/lattice side length&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assumed that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One major source of error comes from the fitting procedure. Ideally, the heat capacity/spin vs temperature curves obtained from simulations would be fitted over the whole temperature range of the simulation, however, the polynomial degree that can be used to fit the simulation curves is limited by the capabilities of the no.polyfit() function and hence the curves must be fitted within a temperature range in the vicinity of the peak heat capacity/spin. This ignores the rest of the heat capacity/spin vs temperature curve so the fitted curve may not accurately describe the true behaviour of each lattice system, which induces an error in the estimate for &amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
With regards to the Monte-Carlo simulation, a major source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes thus some lattices may be insufficiently equilibrated. Insufficient equilibration is normally a result of the algorithm choosing the starting configuration randomly thus the equilibration period for a simulation varies between runs. Another major source of error arises from the random number generation using the random() function in generating the initial configurations and controlling the single spin &#039;flip&#039; dynamics. If the random number generator is poor, it could lead to configurational bias in the simulation other than that due the Monte-Carlo algorithm. Configurational bias thus could lead to the simulated lattice being non-ergodic, another possible source of error, as the probability of sampling each possible configuration of the lattice would not be uniform. The finite size effect was observed to be strong in the lattice systems being modelled as the Curie temperatures varied noticeably with lattice size and therefore this could be another source of error in the estimate for &amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J}&amp;lt;/math&amp;gt;. All of these possible sources of error decrease the accuracy of the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487616</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487616"/>
		<updated>2015-02-16T05:18:52Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin fluctuates over a wider temperature range than the average magnetisation/spin, which changes more abruptly during a phase transition. Furthermore, the energy/spin of the 2x2, 4x4 and 8x8 converge to ca. 0 energy/spin over a smaller temperature range than the energy/spin of the 16x16 and 32x32 lattices. Therefore, for the simulation to properly model long range fluctuations in the lattice during the phase transition the minimum lattice size should be 16x16.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#This function fits a polynomial of degree 8 to C++ simulation heat capacity/spin data, then finds the Curie temperature for each lattice size and writes them #along with the lattice side lengths to a file to be read by ILCurieplot.py&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[] #this list stores the lattice side lengths&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = [] #this list stores the values of the Curie temperatures of lattices with different sizes&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    #the following lines fit a polynomial of degree 8 to C++ simulation heat capacity/spin data in the region 2 K-3 K&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    #the following lines find the Curie temperature, which corresponds to the maximum of the heat capacity/spin vs temperature curve&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
#This function reads data from the file produced by ILTcextract.py, fits the Curie temperature data to a straight line, then plots the fitted line and the Curie #temperature data vs 1/lattice side length&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assumed that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One major source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes thus some lattices may be insufficiently equilibrated. Insufficient equilibration is normally a result of the algorithm choosing the starting configuration randomly thus the equilibration period for a simulation varies between runs. Another major source of error arises from the random number generation using the random() function in generating the initial configurations and controlling the single spin &#039;flip&#039; dynamics. If the random number generator is poor, it could lead to configurational bias in the simulation other than that due the Monte-Carlo algorithm. Configurational bias thus could lead to the simulated lattice being non-ergodic, another source of error, as the probability of sampling each possible configuration of the lattice would not be uniform. The finite size effect was observed to be strong in the lattice systems being modelled as the Curie temperatures varied noticeably with lattice size and therefore this could be another source of error in the estimate for &amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J}&amp;lt;/math&amp;gt;. All of these possible sources of error decrease the accuracy of the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487609</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487609"/>
		<updated>2015-02-16T04:59:26Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#This function fits a polynomial of degree 8 to C++ simulation heat capacity/spin data, then finds the Curie temperature for each lattice size and writes them #along with the lattice side lengths to a file to be read by ILCurieplot.py&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[] #this list stores the lattice side lengths&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = [] #this list stores the values of the Curie temperatures of lattices with different sizes&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    #the following lines fit a polynomial of degree 8 to C++ simulation heat capacity/spin data in the region 2 K-3 K&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    #the following lines find the Curie temperature, which corresponds to the maximum of the heat capacity/spin vs temperature curve&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
#This function reads data from the file produced by ILTcextract.py, fits the Curie temperature data to a straight line, then plots the fitted line and the Curie #temperature data vs 1/lattice side length&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assumed that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One major source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes thus some lattices may be insufficiently equilibrated. Insufficient equilibration is normally a result of the algorithm choosing the starting configuration randomly thus the equilibration period for a simulation varies between runs. Another major source of error arises from the random number generation using the random() function in generating the initial configurations and controlling the single spin &#039;flip&#039; dynamics. If the random number generator is poor, it could lead to configurational bias in the simulation other than that due the Monte-Carlo algorithm. Configurational bias thus could lead to the simulated lattice being non-ergodic, another source of error, as the probability of sampling each possible configuration of the lattice would not be uniform. The finite size effect was observed to be strong in the lattice systems being modelled as the Curie temperatures varied noticeably with lattice size and therefore this could be another source of error in the estimate for &amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J}&amp;lt;/math&amp;gt;. All of these possible sources of error decrease the accuracy of the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487601</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487601"/>
		<updated>2015-02-16T04:37:34Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assumed that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One major source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes thus some lattices may be insufficiently equilibrated. Insufficient equilibration is normally a result of the algorithm choosing the starting configuration randomly thus the equilibration period for a simulation varies between runs. Another major source of error arises from the random number generation using the random() function in generating the initial configurations and controlling the single spin &#039;flip&#039; dynamics. If the random number generator is poor, it could lead to configurational bias in the simulation other than that due the Monte-Carlo algorithm. Configurational bias thus could lead to the simulated lattice being non-ergodic, another source of error, as the probability of sampling each possible configuration of the lattice would not be uniform. The finite size effect was observed to be strong in the lattice systems being modelled as the Curie temperatures varied noticeably with lattice size and therefore this could be another source of error in the estimate for &amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J}&amp;lt;/math&amp;gt;. All of these possible sources of error decrease the accuracy of the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487592</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487592"/>
		<updated>2015-02-16T04:02:33Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assume that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One main source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes thus some lattices may be insufficiently equilibrated. Insufficient equilibration is normally a result of the algorithm choosing the starting configuration randomly thus the equilibration period for a simulation varies between runs. This is similar to anothersourcThis introduces an error in the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt;. Another major source of error was that&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487589</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487589"/>
		<updated>2015-02-16T03:48:16Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assume that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The theoretical exact value thus obtained is noticeably lower than the value obtained from the y- intercept of the plot (&amp;lt;math&amp;gt; \frac{k_{B}T_{c,\infty}}{J} =2.29 (3 d.p.)&amp;lt;/math&amp;gt;) in Figure 26 but both values are still in reasonably good agreement. One main source of error in the estimate value was that a finite number of equilibration steps was chosen in the Monte-Carlo simulation for all lattice sizes. This introduces an error in the &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values used to calculate the heat capacity &amp;lt;math&amp;gt;C_v&amp;lt;/math&amp;gt; because the equilibration period for a simulation varies between runs as a result of the algorithm choosing the starting configuration randomly.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487579</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487579"/>
		<updated>2015-02-16T03:16:27Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle E\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle E^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;/math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assume that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)} = \frac{k_{B}T_{c,\infty}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487573</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487573"/>
		<updated>2015-02-16T03:09:43Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2heatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILpolyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IL2polyfitheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILTcextract.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 &lt;br /&gt;
max_T = 3 &lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 &lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILCurieplot.py&#039;&#039;&#039;&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center| Figure 26: Plot of Peak (Curie) Temperatures vs 1/L for different lattice sizes (L=lattice side length)]]&lt;br /&gt;
&lt;br /&gt;
The plot in Figure 26 was generated by ILCurieplot.py and uses the scaling relation for the Curie temperature of a 2-dimensional lattice of side L T_{C, L}:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where &amp;lt;math&amp;gt;A&amp;lt;/math&amp;gt; is a constant (the slope of the plot in Figure 26) and &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; is the Curie temperature of the infinite (L=&amp;lt;math&amp;gt; \infty&amp;lt;math&amp;gt;) 2-dimensional Ising lattice (the y-intercept of the plot in Figure 26).  The theoretical exact Curie temperature of the infinite 2D Ising lattice can be obtained by solving the following equation&amp;lt;ref&amp;gt;L. Onsager, Crystal Statistics. I. A Two-Dimensional Model with an Order-Disorder Transition, &#039;&#039;Phys. Rev.&#039;&#039;, 1944, &#039;&#039;&#039;65&#039;&#039;&#039;, 117&amp;lt;/ref&amp;gt; for &amp;lt;math&amp;gt;\frac{k_{B}T_{c}}{J}&amp;lt;/math&amp;gt; with the method outlined below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )sinh( \frac{2J&#039;}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;In this experiment it is assume that&amp;lt;math&amp;gt;J=J&#039;=1&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;J&amp;lt;/math&amp;gt;=horizontal coupling constant, &amp;lt;math&amp;gt;J&#039;&amp;lt;/math&amp;gt;=vertical coupling constant&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Therefore, &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ ) = 1&amp;lt;/math&amp;gt; =&amp;gt; &amp;lt;math&amp;gt;sinh^2( \frac{2J}{k_{B}T_{c,\infty}}\ )-1=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)(sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )+1)=0&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;Solving &amp;lt;math&amp;gt; (sinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)=0&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \frac{2}{arcsinh( \frac{2J}{k_{B}T_{c,\infty}}\ )-1)} = \frac{k_{B}T_{c,\infty}}}{J} = 2.269&amp;lt;/math&amp;gt; gives:&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487561</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487561"/>
		<updated>2015-02-16T02:27:40Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice fitted over whole temperature range of simulation.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center|Figure 21: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 2x2 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center|Figure 22: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 4x4 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center|Figure 23: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 8x8 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center|Figure 24: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 16x16 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center|Figure 25: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of degrees 2-8 for 32x32 lattice fitted in the region of 2 K - 3 K.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487553</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487553"/>
		<updated>2015-02-16T02:22:33Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center|Figure 16: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center|Figure 17: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center|Figure 18: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center|Figure 19: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center|Figure 20: Plots of heat capacity/ spin (C++ simulation) vs temperature and polynomial curves of different degrees for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487551</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487551"/>
		<updated>2015-02-16T02:17:00Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 11: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 2x2 lattice.]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 12: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 4x4 lattice.]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 13: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 8x8 lattice.]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 14: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 16x16 lattice.]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center|Figure 15: Plots of heat capacity/ spin (Python simulation) vs temperature and heat capacity/ spin (C++ simulation) vs temperature for 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487548</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487548"/>
		<updated>2015-02-16T02:13:27Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation/spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices. For all the lattices simulated the average energy/spin changes more gradually with temperature. &lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487546</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487546"/>
		<updated>2015-02-16T02:11:01Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487545</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487545"/>
		<updated>2015-02-16T02:08:54Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to enter lowest polynomial degree to fit to heat capacity/ spin vs T data &lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11)) #generates list of 11 polynomial degree values, starting from lower_limit&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)#fits polynomial of degree=deg to heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg)) #plots fitted curve&lt;br /&gt;
    c+=1&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487542</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487542"/>
		<updated>2015-02-16T01:54:28Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N_{+1}!N_{-1}!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N_{+1}=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N_{-1}=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:ILT_c_vs_invLRM1412.png&amp;diff=487540</id>
		<title>File:ILT c vs invLRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:ILT_c_vs_invLRM1412.png&amp;diff=487540"/>
		<updated>2015-02-16T01:52:39Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:ILT c vs invLRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487539</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487539"/>
		<updated>2015-02-16T01:47:41Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487537</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487537"/>
		<updated>2015-02-16T01:46:47Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; in the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\left\langle E^2\right\rangle-\left\langle E\right\rangle ^2 }{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=\left\langle X^2\right\rangle-\left\langle X\right\rangle ^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487536</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487536"/>
		<updated>2015-02-16T01:42:07Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; by the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\&amp;lt;math&amp;gt;\left\langle X^2\right\rangle- \left\langle X\right\rangle\ ^2}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=&amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;-&amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487535</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487535"/>
		<updated>2015-02-16T01:41:34Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; by the following equation:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; C = \frac{\&amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;-&amp;lt;math&amp;gt;\left\langle X\right\rangle\ ^2}{k_B T^2}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=&amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;-&amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487534</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487534"/>
		<updated>2015-02-16T01:39:44Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Heat capacity and locating the Curie temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
The plot in Figure 10 was generated by the script ILheatcapacity.py which calculates the heat capacity from average energy &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and average squared energy &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; values calculated for each lattice size by the montecarlostep() and statistics() functions in the IsingLattice class. The heat capacity &amp;lt;math&amp;gt;C&amp;lt;/math&amp;gt; is expressed in terms of &amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt; by the following equation:&lt;br /&gt;
&amp;lt;math&amp;gt;C = = \frac{\&amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;-&amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt;^2}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
where &amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt;=temperature and the variance, &amp;lt;math&amp;gt;\mathrm{Var}[E]=&amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;-&amp;lt;math&amp;gt;\left\langle X\right\rangle&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
^2 &amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center|Figure 10: Plots of heat capacity/ spin vs temperature for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity, here np.subtract(data[: , 2], data[ : , 1]**2) is the variance&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487528</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487528"/>
		<updated>2015-02-16T01:06:38Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-12J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487527</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487527"/>
		<updated>2015-02-16T01:05:30Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using ILtemperaturerange.py, plots of the average energy/spin vs temperature and average magnetisation/spin vs temperature were generated for lattices of different sizes (2x2, 4x4, 16x16, 32x32) in addition to the plot for the 8x8 lattice. All of the plots (Figure 5-Figure 9) of the average magnetisation/spin vs temperature show a region in which there are large fluctuations in the average magnetisation/spin and over these regions the average magnetisation per spin drops from ca. +1 to ca. 0, indicating a phase transition from the ferromagnetic phase to the paramagnetic phase of the lattice systems. At lower temperatures, the stabilisation energy due exchange interactions between parallel atomic spins is greater than the thermal energy available in the system so spins tend to align &amp;lt;ref&amp;gt;D. J. Griffiths, &#039;&#039;Introduction to Quantum Mechanics&#039;&#039;, Pearson Prentice Hall, Massachusetts, 2nd edn., 2004, pp. 207–208&amp;lt;/ref&amp;gt; whereas at high temperatures the thermal energy available overcomes the stabilisation energy due to exchange effects between parallel atomic spins causing spins to &amp;quot;flip&amp;quot; and become anti-aligned, thus increasing the system entropy. The plots show that the temperature at which the onset of the phase transition occurs increases on changing the lattice size from 2x2 to 32x32. This is due to small lattices having lower stabilisation energies compared to large lattices, since small lattices will have fewer atomic spins and thus fewer exchange interactions between aligned spins than in large lattices; hence, aligned spins in small lattices require less thermal energy to become anti-aligned than in large lattices.&lt;br /&gt;
&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487506</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487506"/>
		<updated>2015-02-15T23:59:07Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
NB: In this task ILtimetrial.py was run on a computer with a HP intel CORE i7 vPro processor&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487503</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487503"/>
		<updated>2015-02-15T23:51:49Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as there are large fluctuations in the magnetisation/spin which drops from ca. 1 to ca. 0 within the 2 K - 3 K, and the energy/spin increases from ca. -2 to ca. 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487501</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487501"/>
		<updated>2015-02-15T23:48:58Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of system size */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as the magnetisation/spin fluctuates rapidly and drops from ca. 1 at 2 K to ca. 0 at 3 K, and the energy/spin increases from -2 to near 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 5: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 2x2 lattice.]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 6: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 4x4 lattice.]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 7: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 8: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 16x16 lattice.]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 9: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of a 32x32 lattice.]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487495</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487495"/>
		<updated>2015-02-15T23:36:12Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as the magnetisation/spin fluctuates rapidly and drops from ca. 1 at 2 K to ca. 0 at 3 K, and the energy/spin increases from -2 to near 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487494</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487494"/>
		<updated>2015-02-15T23:35:47Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as the magnetisation/spin fluctuates rapidly and drops from ca. 1 at 2 K to ca. 0 at 3 K, and the energy/spin also increases from -2 to near 0 in this region. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487491</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487491"/>
		<updated>2015-02-15T23:34:37Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
The plot shows that the 8x8 lattice undergoes a phase transition in the region of 2 K - 3 K as the magnetisation/spin fluctuates rapidly and drops from ca. 1 at 2 K to ca. 0 at 3 K. The size of the error bars for the magnetisation/spin and energy/spin suggests there is only a small spread in the magnetisation/spin and energy/spin values at each temperature.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_32x32_140000_hc_CDataRM1412.png&amp;diff=487486</id>
		<title>File:IL 32x32 140000 hc CDataRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_32x32_140000_hc_CDataRM1412.png&amp;diff=487486"/>
		<updated>2015-02-15T23:25:45Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL 32x32 140000 hc CDataRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_16x16_140000_hc_CDataRM1412.png&amp;diff=487485</id>
		<title>File:IL 16x16 140000 hc CDataRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_16x16_140000_hc_CDataRM1412.png&amp;diff=487485"/>
		<updated>2015-02-15T23:25:29Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL 16x16 140000 hc CDataRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_8x8_140000_hc_CDataRM1412.png&amp;diff=487484</id>
		<title>File:IL 8x8 140000 hc CDataRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_8x8_140000_hc_CDataRM1412.png&amp;diff=487484"/>
		<updated>2015-02-15T23:25:16Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL 8x8 140000 hc CDataRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_4x4_140000_hc_CDataRM1412.png&amp;diff=487482</id>
		<title>File:IL 4x4 140000 hc CDataRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_4x4_140000_hc_CDataRM1412.png&amp;diff=487482"/>
		<updated>2015-02-15T23:24:59Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL 4x4 140000 hc CDataRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_2x2_140000_hc_CDataRM1412.png&amp;diff=487479</id>
		<title>File:IL 2x2 140000 hc CDataRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_2x2_140000_hc_CDataRM1412.png&amp;diff=487479"/>
		<updated>2015-02-15T23:23:25Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL 2x2 140000 hc CDataRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_h_c_allRM1412.png&amp;diff=487476</id>
		<title>File:IL h c allRM1412.png</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:IL_h_c_allRM1412.png&amp;diff=487476"/>
		<updated>2015-02-15T23:21:17Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: Rm1412 uploaded a new version of &amp;amp;quot;File:IL h c allRM1412.png&amp;amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487474</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487474"/>
		<updated>2015-02-15T23:18:04Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
From the plots in task 12 it was observed that large lattice systems, such as a 32x32 lattice only reached an equilibrium state after around 80000 steps and it was determined that the first 40000 steps of the simulation would be ignored when calculating the averages of self.E, self.E2, self.M and self.M2. Therefore, simulations in later calculations will be performed with 140000 steps. The plot in Figure 4 was obtained with ILtemperaturerange.py which performed a 140000 step simulation of an 8x8 lattice in the temperature range 0.25 K - 5 K, plotting the average energy/spin and average magnetisation/spin at 0.1 K intervals. &lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center|Figure 4: Plot of average energy/spin vs temperature and average magnetisation/spin vs temperature of an 8x8 lattice.]]&lt;br /&gt;
From&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&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;
&lt;br /&gt;
n_rows = int(input(&amp;quot;number of rows: &amp;quot;)) #prompts user to input the number of rows in the lattice&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(n_rows, n_rows)&lt;br /&gt;
il.lattice = np.ones((n_rows, n_rows))&lt;br /&gt;
spins = n_rows**2&lt;br /&gt;
runtime = 140000&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
#the errE and errM lists store the standard errors in the energy/spin and magnetisation/spin at each temperature, respectively&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following 2 lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/(spins*(n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/(spins*(n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&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;
magax.plot(temps, np.array(magnetisations)/spins, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
&lt;br /&gt;
pl.legend()&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487467</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487467"/>
		<updated>2015-02-15T22:52:19Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 0.25 K and 0.5 K show  that no noticeable equilibration period is observed for the 2x2 lattice system and only a very short equilibration period of ca. 1000 steps is observed for the 8x8 lattice whereas the 32x32 lattice system exhibits a much longer equilibration period of ca. 70000-80000 steps. At 0.25 K and 0.5 K, the lattice systems are close to absolute zero so the equilibrium states of each lattice are expected to have average energy per spin of approximately &amp;lt;math&amp;gt;E= -DJ= -2&amp;lt;/math&amp;gt; and average magnetisation per spin of approximately &amp;lt;math&amp;gt;M=-1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;, depending on the initial configuration. The energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps curves both converge to the expected energy per spin and magnetisation per spin values at 0.25 K and 0.5 K for all of the lattices simulated therefore showing that the lattices were able to reach the equilibrium state. Plots of energy per spin vs Monte-Carlo steps and magnetisation per spin vs Monte-Carlo steps from simulations at 1.0 K and 1.75 K show that only the 8x8 and 32x32 lattices were able to reach an equilibrium state and for both lattices the length of the equilibration periods were similar to those at 0.25 K and 0.5 K, however the 2x2 lattice exhibits large, rapid fluctuations in its energy per spin and magnetisation suggesting its Curie temperature has been reached and the 2x2 lattice system is undergoing a phase transition. At all temperatures at which the simulation was performed, the 32x32 lattice system exhibited a steep drop in its energy per spin and magnetisation per spin only within the first 40000-50000 steps of the simulation, which is far longer than the typical equilibration periods of the 8x8 lattice within the temperature range of the simulations. Therefore, it is reasonable to ignore the first 40000 steps of the simulation when calculating the averages of self.E, self.E2, self.M and self.M2. &lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines determine the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487435</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487435"/>
		<updated>2015-02-15T21:35:33Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The effect of temperature */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure 3 show that there is a period in the simulation before the lattice reaches its equilibrium state. The montecarlostep(self, T) function in its current form calculates the running sums of the attributes self.E, self.E2, self.M and self.M2 from the start of the simulation. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that during the calculation of the averages aveE, aveE2, aveM and aveM2 the energy and magnetisation values from the equilibration period are ignored. Before any corrections to the montecarlostep(self, T) and statistics(self) functions can be made the number of equilibration steps during which the running sum is not calculated must be determined. The script ILfinalframe.py was used to run simulations of 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. This temperature range was chosen so that all simulations were run below the observed critical temperatures of 2-dimensional Ising lattices (ca. 2-2.5 K)&amp;lt;ref&amp;gt;D.A. Ajadi , L.A. Sunmonu , O.A. Aremu , and J.A. Oladunjoye, 2D-Ising model for Simulation of Critical Phenomena of NiOFe&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;O&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; using Monte Carlo Technique, &#039;&#039;International Journal of Innovation and Applied Studies&#039;&#039;, 2014, &#039;&#039;&#039;9&#039;&#039;&#039;, 1336-1344&amp;lt;/ref&amp;gt;. The plots thus obtained are shown below:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinalframe.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487424</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487424"/>
		<updated>2015-02-15T20:58:34Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-\left\langle t\right\rangle\ )^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;\left\langle t\right\rangle\ &amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;\left\langle t\right\rangle\ =0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487422</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487422"/>
		<updated>2015-02-15T20:55:14Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the standard error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation in IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time observed for the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487421</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487421"/>
		<updated>2015-02-15T20:46:21Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Importance sampling and the Monte-Carlo method */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py. Top: plot of current lattice configuration, middle: plot ofinteraction energy per spin (E/spin) vs number of steps, bottom: plot of net magnetisation per spin (M/spin) vs number of steps]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487420</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487420"/>
		<updated>2015-02-15T20:44:22Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Importance sampling and the Monte-Carlo method */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes more time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions in class IsingLattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure 3: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure 3. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random, and this configuration is very likely higher in energy than the lowest energy configuration.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of ILanim.py from statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487417</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487417"/>
		<updated>2015-02-15T20:36:01Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin changing direction in row i and column j and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487407</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487407"/>
		<updated>2015-02-15T19:32:14Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Calculating the energy and magnetisation of the lattice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration (Figure 2) agree perfectly with the values calculated by the functions. Therefore the energy(self) and magnetisation(self) functions can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487406</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487406"/>
		<updated>2015-02-15T19:30:07Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Calculating the energy and magnetisation of the lattice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure 2) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in later calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487405</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487405"/>
		<updated>2015-02-15T19:29:47Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure 1 calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure 1: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure 2) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in subsequent calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487404</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487404"/>
		<updated>2015-02-15T19:29:16Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* Calculating the energy and magnetisation of the lattice */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure Y calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure Y: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure 2) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in subsequent calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure 2: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487403</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487403"/>
		<updated>2015-02-15T19:24:32Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure Y calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure Y: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure X) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in subsequent calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure X: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487402</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487402"/>
		<updated>2015-02-15T19:22:31Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3*1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999 or 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1 or 999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure Y calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure Y: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure X) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in subsequent calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure X: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487401</id>
		<title>Rep:Mod:YR3CMPRM1412</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:Mod:YR3CMPRM1412&amp;diff=487401"/>
		<updated>2015-02-15T19:21:56Z</updated>

		<summary type="html">&lt;p&gt;Rm1412: /* The Ising Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The Ising Model==&lt;br /&gt;
&#039;&#039;&#039;Introduction&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Ising model, formulated in 1920 by Wilhelm Lenz, is a lattice model and the simplest mathematical description of ferromagnetic materials. Ising first used the model to study ferromagnetism but it has since  been used to model other systems that exhibit co-operative phenomena and examples of systems studied thus far include surfaces of separation at phase boundaries&amp;lt;ref&amp;gt;G. Gallavotti, The Phase Separation Line in the Two-Dimensional Ising Model, &#039;&#039;Commun. math. Phys.&#039;&#039;, 1972, &#039;&#039;&#039;27&#039;&#039;&#039;, 103-136&amp;lt;/ref&amp;gt; and gas-liquid systems at their critical point&amp;lt;ref&amp;gt;C. K. Hu, Historical Review on Analytic, Monte Carlo, and Renormalization Group Approaches to Critical Phenomena of Some Lattice Models, &#039;&#039;Chinese J. Phys.&#039;&#039;, 2014, &#039;&#039;&#039;52&#039;&#039;&#039;, 1-67&amp;lt;/ref&amp;gt;. For this experiment, the Ising model will be used to study ferromagnetic materials. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Calculating the interaction energy and net magnetisation for example lattices&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In the lowest energy configuration of an Ising lattice in D dimensions all spins are aligned and each spin is interacting with 2*D spins in adjacent cells. For a lattice with N spins, the energy of its lowest energy configuration can be written in terms of D and N by noting that the total number of interactions is:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j = 2DN&amp;lt;/math&amp;gt; &amp;lt;/div&amp;gt;&lt;br /&gt;
and substituting this expression into the original equation for the interaction energy:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
which gives: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-\frac{1}{2} 2DNJ=-DNJ&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
The number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; available in the lowest energy configuration (the multiplicity of the configuration) is 2 as each spin can be +1 or -1. The entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; of a configuration can be calculated using the Boltzmann equation: &lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(W)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;=Boltzmann constant. For the lowest energy configuration of the Ising lattice &amp;lt;math&amp;gt;W=2&amp;lt;/math&amp;gt; therefore:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;S=k_B ln(2)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
In order to excite the lowest energy configuration of the Ising lattice into higher energy configurations one or more spins in the lattice must change in direction, which changes the interaction energy, entropy and net magnetisation in the lattice. For example, the energy of a 3-dimensional lattice (D=3) with 1000 spins (N=1000) in the lowest energy configuration is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=-DNJ=-3x1000J=-3000J&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
When one spin changes direction in the lowest energy configuration of an Ising lattice the interaction energies of spin changing direction and 2*D adjacent spins changes. The interaction energy of each spin is defined as:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;E=- \frac{1}{2}J\ s_{ij}(s_{a, ij}+s_{b, ij}+s_{l, ij}+s_{r, ij})&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt; &lt;br /&gt;
where &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;=spin in row i and column j changing direction and &amp;lt;math&amp;gt;s_{a, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}&amp;lt;/math&amp;gt; are the adjacent spins above, below, left of and right of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt;, respectively. On changing the the direction of &amp;lt;math&amp;gt;s_{ij}&amp;lt;/math&amp;gt; the products &amp;lt;math&amp;gt;s_{a, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{b, ij}.s_{ij}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;s_{l, ij}.s_{ij}&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;s_{r, ij}.s_{ij}&amp;lt;/math&amp;gt; change sign, which means that &amp;lt;math&amp;gt; \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&amp;lt;/math&amp;gt; decreases by 12. Therefore, the change in energy &amp;lt;math&amp;gt;dE&amp;lt;/math&amp;gt; associated with one spin changing direction in the lowest energy configuration of the lattice is &amp;lt;math&amp;gt;dE=-6J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change in the lattice entropy arises due to the number of microstates &amp;lt;math&amp;gt;W&amp;lt;/math&amp;gt; increasing on changing the direction of one spin in the lowest energy configuration. In the case of the 3-dimensional lattice with 1000 spins the number of microstates the lowest energy configuration has 2 microstates and, therefore, changing the direction of one spin results in 2000 microstates, as each spin in the lattice can change direction with equal probability and there are 2 possible spin directions (+1 or -1). Alternatively, the same result for the number of microstates in a lattice can be obtained using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;W=2\frac{N!}{N(+1)!N(-1)!}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the factor of 2 accounts for the possibility of having all +1 or all -1 spins in the initial configuration, and the number of atoms with spin +1 and spin -1 are &amp;lt;math&amp;gt;N(+1)=999 or 1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;N(-1)=1 or 999&amp;lt;/math&amp;gt;, respectively. The entropy gained from changing the direction of one spin in the lowest energy configuration is equal to the difference &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; between the entropies of the lowest energy configuration &amp;lt;math&amp;gt;S_0&amp;lt;/math&amp;gt; and the next higher energy configuration of the lattice &amp;lt;math&amp;gt;S_1&amp;lt;/math&amp;gt;. For the 3-dimensional lattice with 1000 spins, the entropy change &amp;lt;math&amp;gt;dS&amp;lt;/math&amp;gt; can be calculated using the Boltzmann equation thus:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dS=k_Bln(2000)-k_Bln(2)=k_Bln(2000/2)=k_Bln(1000)&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 3&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The net magnetisation of an Ising lattice is given by:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;M=\sum_i^N\ s_i &amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where s_i is the spin in the ith lattice. For example, the net magnetisations of the 1x5 (3 s= +1 spins, 2 s= -1 spins) and 5x5 (13 s= +1 spins, 12 s= -1 spins) lattices shown in Figure Y calculated using the above equation are both &amp;lt;math&amp;gt;M=1&amp;lt;/math&amp;gt;. At 0 K (absolute zero), the spins in the lattice will tend to adopt the lowest energy configuration (with all spins aligned) and therefore at 0 K a lattice with N spins will have a net magnetisation &amp;lt;math&amp;gt;M=N&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-N&amp;lt;/math&amp;gt;. For a 3-dimensional lattice with 1000 spins at 0 K &amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt; or &amp;lt;math&amp;gt;M=-1000&amp;lt;/math&amp;gt;.&lt;br /&gt;
[[File:ThirdYearCMPExpt-IsingSketch.png|200px|thumb|right|Figure Y: 1x5 (1D), 5x5 (2D) and 5x5 (3D) Ising lattices. Red cells and blue cells contain atoms with spins s=+1 and s=-1, respectively. Image was taken from &amp;lt;ref&amp;gt;Third year CMP compulsory experiment/Introduction to the Ising model, https://wiki.ch.ic.ac.uk/wiki/index.php?title=Third_year_CMP_compulsory_experiment/Introduction_to_the_Ising_model, 7&amp;lt;sup&amp;gt;th&amp;lt;/sup&amp;gt; February 2015&lt;br /&gt;
&amp;lt;/ref&amp;gt;]]&lt;br /&gt;
&lt;br /&gt;
==Calculating the energy and magnetisation of the lattice==&lt;br /&gt;
&#039;&#039;&#039;Modelling the Ising lattice&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the definitions of the interaction energy and net magnetisation from the Ising Model functions were written to calculate the magnetisation energy and net magnetisation within the IsingLattice class in Python. Initially, the functions were written without the use of functions from the NumPy module. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 4&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate interaction energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
        #this function calculates the interaction energy&lt;br /&gt;
        energy, l, c, r = 0.0, self.lattice, 0, 0&lt;br /&gt;
        while r&amp;lt;self.n_rows:&lt;br /&gt;
        #[r, c] indexes a cell at row r and column c in the Ising lattice l. [r,c-self.n_cols+1] indexes the cell to the right and [r,c-1]&lt;br /&gt;
        #indexes the cell to the left of l[r,c]. In a similar way, [r-self.n_rows+1,c] indexes the cell above and [r-1,c] indexes the cell         #below&lt;br /&gt;
        l[r,c]&amp;quot;&lt;br /&gt;
            energy+=-0.5*l[r,c]*(l[r,c-self.n_cols+1]+l[r,c-1]+l[r-self.n_rows+1,c]+l[r-1,c])&lt;br /&gt;
            c+=1&lt;br /&gt;
            if c==self.n_cols:&lt;br /&gt;
            #this if statement changes the row in which the above sum if performed if the while loop has reached the last element &lt;br /&gt;
            #in the current row&lt;br /&gt;
                c,r=0, r+1&lt;br /&gt;
        return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The value of energy returned is given in reduced units, which can be converted to a value in standard energy units by a factor proportional to k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; (Boltzmann constant). It is assumed that J=k&amp;lt;sub&amp;gt;B&amp;lt;/sub&amp;gt; and in reduced units J=1.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
        #this function calculates the net magnetisation&lt;br /&gt;
        magnetisation=0&lt;br /&gt;
        for row in self.lattice:&lt;br /&gt;
        #this for loop accesses each row of the lattice&amp;quot;&lt;br /&gt;
            for spin in row:&lt;br /&gt;
                #this for loop accesses each cell (column) in each row of the lattice&amp;quot;&lt;br /&gt;
                magnetisation+=spin&lt;br /&gt;
        return magnetisation&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A value for net magnetisation was obtained by calculating the sum of spins in each row which were then summed to give the net magnetisation of the lattice. &lt;br /&gt;
&lt;br /&gt;
It is important to note that both energy and magnetisation values are calculated assuming only &amp;quot;adjacent&amp;quot; spins interact in the lattice. Spins are adjacent if their cells are vertically or horizontally next to each other, thus spins in cells diagonal to each other do not interact in the Ising lattice. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 5&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Checking the correctness of the energy(self) and magnetisation(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The script ILcheck.py was used to compare the values calculated from the energy(self) and magnetisation(self) functions with the expected values for the magnetisation energy and net magnetisation of a 4x4 Ising lattice. The expected values for the magnetisation energy and net magnetisation of each configuration thus obtained (Figure X) agree perfectly with the calculated values. Therefore the energy(self) and magnetisation(self) functions written can be used in subsequent calculations. &lt;br /&gt;
&lt;br /&gt;
[[File:ILcheckResultRM1412.jpeg|thumb|upright=2|Figure X: Plot of the minimum energy (left), random (center) and maximum energy (right) of a 4x4 Ising lattice computed by ILcheck.py. Blue and red squares denote cells occupied by atoms with spins s=+1 and s=-1, respectively.]]&lt;br /&gt;
&lt;br /&gt;
==Importance sampling and the Monte-Carlo method==&lt;br /&gt;
&#039;&#039;&#039;Calculating the average interaction energy and magnetisation&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 6&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At temperatures above 0 K thermal energy available causes fluctuations in the number of +1 and -1 spins in the lattice, and thus the system no longer has only 2 microstates. For a system with 100 spins, the number of microstates (configurations) available is &amp;lt;math&amp;gt;W=2^{100}&amp;lt;/math&amp;gt;. Therefore, to find out which configurations contribute to the average interaction energy and average net magnetisation of the lattice at a given temperature several configurations need to be analysed. One approach to obtain the averages is to calculate the interaction energies, net magnetisations and Boltzmann factors of all the possible configurations of the lattice and then use these values to compute the average interaction energy and average net magnetisation. Such an approach, however, becomes computationally expensive and time consuming as the lattice being analysed increases in size. For example, if the average net magnetisation was calculated on a computer that could analyse 10&amp;lt;sup&amp;gt;9&amp;lt;/sup&amp;gt; configurations per second it would take ca. 1.27x10&amp;lt;sup&amp;gt;21&amp;lt;/sup&amp;gt; s, or ca. 4.02x10&amp;lt;sup&amp;gt;13&amp;lt;/sup&amp;gt; years, to complete the calculation. Not all the possible configurations of a lattice will contribute to its average interaction energy and average magnetisation at a given temperature therefore some configurations can be ignored when calculating the averages. This can be achieved using importance sampling and the Monte-Carlo method.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 7&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;montecarlostep(self, T) and statistics(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;def montecarlostep(self, T):&lt;br /&gt;
        #this function performs a single Monte Carlo step&lt;br /&gt;
        a0, energy, magnetisation= self.lattice, self.energy(), 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(1, self.n_rows))&lt;br /&gt;
        random_j = np.random.choice(range(1, self.n_cols))&lt;br /&gt;
        #the following line flips one spin at [random_i, random_j] in the lattice&lt;br /&gt;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy #E1= energy of random configuration&lt;br /&gt;
        #the following line will choose a random number in the range [0,1)&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0: &lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1 #random configuration is accepted&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
            #The line below changes self.lattice back to a0 if the condition in the elif statement in the line above is met&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j] #random configuration is rejected&lt;br /&gt;
        #the following lines update the running sum of E, E2, M and M2&lt;br /&gt;
        self.E=self.E+energy&lt;br /&gt;
        self.E2=self.E2+(energy)**2&lt;br /&gt;
        self.M=self.M+magnetisation&lt;br /&gt;
        self.M2=self.M2+(magnetisation)**2&lt;br /&gt;
        self.n_cycles+=1&lt;br /&gt;
        # the function should end by calculating and returning both the energy and magnetisation:&lt;br /&gt;
        return energy, magnetisation&lt;br /&gt;
        &lt;br /&gt;
    def statistics(self):&lt;br /&gt;
        #this function calculates the values for the averages of E, E*E (E2), M, M*M (M2), and returns them along with &lt;br /&gt;
        #the number of cycles n_cycles&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, self.n_cycles&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 8&#039;&#039;&#039;&lt;br /&gt;
[[File:ILanimResultRM1412.jpeg|310px|thumb|right|alt=|Figure Z: Simulation of 8x8 lattice with the Monte-Carlo algorithm using ILanim.py.]]&lt;br /&gt;
&#039;&#039;&#039;Output of a simulation with ILanim.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A simulation of an 8x8 lattice at 0.5 K for 3122 steps was run using ILanim.py to yield the plots in Figure Z. The plots of the interaction energy per spin (E/spin) vs number of steps and net magnetisation per spin (M/spin) vs number of steps show that, at 0.5 K, the lattice reaches its equilibrium state after around 400 steps of the simulation. After this equilibration period, no significant fluctuations in E/spin and M/spin are observed, and both E/spin and M/spin remain roughly constant. The averages &amp;lt;E/spin&amp;gt;=-1.9321748878923768 and &amp;lt;M/spin&amp;gt;=-0.9740150544522742 obtained at the end of the simulation period are almost in perfect agreement with the expected values for the interaction energy per spin and the magnetisation per spin for an 8x8 lattice at 0 K, which are E/spin=-128/64=-2 and M/spin=-64/64=-1. Below the Curie temperature T&amp;lt;sub&amp;gt;C&amp;lt;/sub&amp;gt; of a ferromagnetic material it is expected that spontaneous magnetisation in the material will occur and that the average magnetisation &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;. The plot of M/spin vs number of steps, however, shows that the magnetisation in the lattice is initially closer to 0 than expected; this is because the code generates the starting configuration at random.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Output of statistics(self) function&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
(&#039;Cycles elapsed = &#039;, 3122)&lt;br /&gt;
Averaged quantities:&lt;br /&gt;
(&#039;E = &#039;, -1.9321748878923768)&lt;br /&gt;
(&#039;E*E = &#039;, 3.810370455637412)&lt;br /&gt;
(&#039;M = &#039;, -0.9740150544522742)&lt;br /&gt;
(&#039;M*M = &#039;, 0.9618102128042921)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Accelerating the code==&lt;br /&gt;
&#039;&#039;&#039;Task 9&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The energy(self) and magnetisation(self) functions were tested using ILtimetrial.py which measured the time taken to run the montecarlostep(self, T) function 2000 times. The average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py and the error in this average &amp;lt;math&amp;gt;dt&amp;lt;/math&amp;gt; was calculated using:&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&amp;lt;math&amp;gt;dt=\frac{1}{N} \sqrt{ \sum_i^N\ (t_i-&amp;lt;t&amp;gt;)^2 }=\frac{\sigma}{\sqrt{N}}&amp;lt;/math&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
where the number of repeats &amp;lt;math&amp;gt;N=1000&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;\sigma&amp;lt;/math&amp;gt; is the standard deviation. For the original energy(self) and magnetisation(self) functions the average time in seconds &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=4.03727837495\pm0.0875591890212 s&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 10&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The initial scripts for the energy(self) and magnetisation(self) functions were ran in the Python interpreter and thus not as fast as versions of these functions incorporating NumPy functions, which runs code written in C. The energy(self) and magnetisation(self) functions were rewritten using NumPy functions to optimise them for speed. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate magnetisation energy&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def energy(self):&lt;br /&gt;
    #this function calculates the interaction energy&lt;br /&gt;
    energy=-(np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=0)))+np.sum(np.multiply(self.lattice, np.roll(self.lattice, 1, axis=1))))&lt;br /&gt;
    return energy&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimised function to calculate net magnetisation&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;def magnetisation(self):&lt;br /&gt;
    #this function calculates the net magnetisation&lt;br /&gt;
    return np.sum(self.lattice)&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 11&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The optimised energy(self) and magnetisation(self) functions were tested using ILtimetrial.py and the average time taken to perform 2000 Monte-Carlo steps &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;&amp;lt;/math&amp;gt; was calculated from times obtained &amp;lt;math&amp;gt;t_i&amp;lt;/math&amp;gt; from 1000 repeat runs of ILtimetrial.py. This average time was calculated to be &amp;lt;math&amp;gt;&amp;lt;t&amp;gt;=0.2235433141795\pm0.00246861627917 s&amp;lt;/math&amp;gt; which is significantly faster than the average time taken when using the original energy(self) and magnetisation(self) functions.&lt;br /&gt;
&lt;br /&gt;
==The effect of temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 12&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The plots of E/spin vs number of steps and M/spin vs number of steps in Figure Z show that there is a period in the simulation before the lattice reaches its equilibrium state. So far, the montecarlostep(self, T) and statistics(self) functions calculate the averages from the start of the simulation which means the averages obtained thus far are not very accurate. In order to obtain more accurate averages, corrections to the montecarlostep(self, T) and statistics(self) functions are required so that the energy and magnetisation values from the equilibration period are ignored when calculating the averages. To determine the minimum number of steps from which energy and magnetisation values are ignored simulations were performed using ILfinaltemperature.py with 2x2, 8x8 and 32x32 lattices at 0.25 K, 0.5 K, 1 K and 1.75 K. Plots of E/spin vs number of steps and M/spin vs number of steps were obtained for each system simulated, which are shown below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Plots from ILfinaltemperature.py&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellspacing=&amp;quot;1&amp;quot; cellpadding=&amp;quot;10&amp;quot; style=&amp;quot;margin: 1em auto 1em auto;&amp;quot;&lt;br /&gt;
!T (K, horizontal) vs runs (vertical)&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |2x2&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |8x8&lt;br /&gt;
!scope=&amp;quot;col&amp;quot; width=&amp;quot;200px&amp;quot; style=&amp;quot;text-align: center;&amp;quot; |32x32&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.25 &lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_025KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_025KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |0.5&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_050KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_050KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_100KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_100KRM1412.png|200px]]&lt;br /&gt;
|-&lt;br /&gt;
|style=&amp;quot;text-align: center;&amp;quot; |1.75&lt;br /&gt;
|height=”200px”|[[File:IL_2_2_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_8_8_100000_175KRM1412.png|200px]]&lt;br /&gt;
|height=”200px”|[[File:IL_32_32_100000_175KRM1412.png|200px]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
From the equation&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Corrected montecarlostep(self, T) and statistic(self) functions&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
        a0, energy, magnetisation= self.lattice, self.energy(), 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;
        self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        a1, E1, M1= self.lattice, self.energy(), self.magnetisation()&lt;br /&gt;
        dE=E1-energy&lt;br /&gt;
        #the following line will choose a random number in the range [0,1) for you&lt;br /&gt;
        random_number = np.random.random()&lt;br /&gt;
        if dE&amp;lt;=0:&lt;br /&gt;
            a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
        elif dE&amp;gt;0:&lt;br /&gt;
            if np.exp(-dE/T)&amp;gt;=random_number:&lt;br /&gt;
                a0, energy, magnetisation= a1, E1, M1&lt;br /&gt;
            elif np.exp(-dE/T)&amp;lt;random_number:&lt;br /&gt;
                self.lattice[random_i, random_j]=-self.lattice[random_i, random_j]&lt;br /&gt;
        #the following lines control the step after which the running sum is performed&lt;br /&gt;
        if self.n_cycles&amp;lt;40000:&lt;br /&gt;
            self.n_cycles+=1 #increases n_cycles until it reaches end of equilibration period (40000 steps)&lt;br /&gt;
        elif self.n_cycles&amp;gt;=40000:&lt;br /&gt;
            self.E=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;
        # the function should end by calculating and returning both the energy and magnetisation:&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;
        #along with the number of cycles&lt;br /&gt;
        #self.n_cycles is subtracted by 40000 as the running sums of E, E2, M and M2 are not performed until the 40000th step&lt;br /&gt;
        aveE=self.E/(self.n_cycles-40000)&lt;br /&gt;
        aveE2=self.E2/(self.n_cycles-40000)&lt;br /&gt;
        aveM=self.M/(self.n_cycles-40000)&lt;br /&gt;
        aveM2=self.M2/(self.n_cycles-40000)&lt;br /&gt;
        return (aveE, aveE2, aveM, aveM2, (self.n_cycles-40000))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 13&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILtemperaturerange.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&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;
&lt;br /&gt;
n_rows = int(raw_input(&amp;quot;number of rows: &amp;quot;))&lt;br /&gt;
n_cols = int(raw_input(&amp;quot;number of columns: &amp;quot;))&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 = int(raw_input(&amp;quot;number of cycles: &amp;quot;))&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.1)&lt;br /&gt;
energies = []&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
errE=[]&lt;br /&gt;
errM=[]&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 = 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;
    #The following lines calculates the standard error in the average energy and average magnetisation&lt;br /&gt;
    errE.append(((aveE2-(aveE**2))**0.5)/((spins*n_cycles)**0.5))&lt;br /&gt;
    errM.append(((aveM2-(aveM**2))**0.5)/((spins*n_cycles)**0.5))&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.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.set_ylim([-2.1, 2.1])&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins, yerr=np.array(errE), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&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;
magax.plot(temps, np.array(magnetisations)/spins)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins, yerr=np.array(errM), marker=&amp;quot;o&amp;quot;) #plots error bars for each point in E/spin vs T&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_cols)+&amp;quot;.dat&amp;quot;, final_data)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==The effect of system size==&lt;br /&gt;
&#039;&#039;&#039;Task 14&#039;&#039;&#039;&lt;br /&gt;
[[File:IL 2x2 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 4x4 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 8x8 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 16x16 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL 32x32 140000 e m t rangeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==Heat capacity and locating the Curie temperature==&lt;br /&gt;
&#039;&#039;&#039;Task 15&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_h_c_allRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ILheatcapacity.py&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots heat capacity/spin vs T for 2x2, 4x4, 8x8, 16x16 and 32x32 lattices&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows = 2&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
labels, plot_data = [], []&lt;br /&gt;
while n_rows &amp;lt;= 32:&lt;br /&gt;
    data = np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from .dat file as an array&lt;br /&gt;
    heat_capacity = np.subtract(data[: , 2], data[ : , 1]**2)/((n_rows*data[ : , 0])**2) #calculates heat capacity&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    plot_data.append(data[ : , 0]) #appends temperatures in range [0.25, 5] K to plot_data&lt;br /&gt;
    plot_data.append(heat_capacity) #appends heat capacities calculated in heat_capacity variable&lt;br /&gt;
    labels.append(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;lattice&amp;quot;) #appends labels generated for each lattice size&lt;br /&gt;
    n_rows=2*n_rows   &lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
hcax.plot(plot_data[0], np.array(plot_data[1]), &amp;quot;r&amp;quot;, label=str(labels[0])) #plots heat capacity/spin vs T for 2x2 lattice&lt;br /&gt;
hcax.plot(plot_data[2], np.array(plot_data[3]), &amp;quot;g&amp;quot;, label=str(labels[1])) #plots heat capacity/spin vs T for 4x4 lattice&lt;br /&gt;
hcax.plot(plot_data[4], np.array(plot_data[5]), &amp;quot;b&amp;quot;, label=str(labels[2])) #plots heat capacity/spin vs T for 8x8 lattice&lt;br /&gt;
hcax.plot(plot_data[6], np.array(plot_data[7]), &amp;quot;k&amp;quot;, label=str(labels[3])) #plots heat capacity/spin vs T for 16x16 lattice&lt;br /&gt;
hcax.plot(plot_data[8], np.array(plot_data[9]), &amp;quot;m&amp;quot;, label=str(labels[4])) #plots heat capacity/spin vs T for 32x32 lattice&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 16&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this script plots data from simulations in Python and C++ of a 2-dimensional square Ising lattice&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=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows in lattice&lt;br /&gt;
data=np.loadtxt(str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data generated by ILtemperaturerange.py&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;) #loads data from C++ simulation&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(2,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/ spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature (K)&amp;quot;)&lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by ILtemperaturerange.py&lt;br /&gt;
hcax.plot(data[ : ,0], np.subtract(data[ : ,2], data[ : ,1]**2)/((n_rows*data[ : ,0])**2), &amp;quot;b&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice&amp;quot;) &lt;br /&gt;
#plots heat capacity/ spin vs T from data generated by C++ simulation&lt;br /&gt;
hcax.plot(data_from_C[ : , 0], data_from_C[ : , 5], &amp;quot;k&amp;quot;, label = str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; lattice (C++ data)&amp;quot;) &lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_140000_hc_CDataRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 17&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_32x32_polyfit_freeRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;))&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;000066&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;, &amp;quot;660000&amp;quot;, &amp;quot;663366&amp;quot;, &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;, &amp;quot;FF9933&amp;quot;, &amp;quot;CCFFFF&amp;quot;]  &lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]    &lt;br /&gt;
c=0&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;))&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+11))&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    fit = np.polyfit(T, C, deg)&lt;br /&gt;
    T_min = np.min(T)&lt;br /&gt;
    T_max = np.max(T)&lt;br /&gt;
    T_range = np.linspace(T_min, T_max, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Task 18&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#this function fits a series of polynomial curves to heat capacity data obtained from simulations in C++&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=int(input(&amp;quot;Enter number of rows: &amp;quot;)) #prompts user to input number of rows&lt;br /&gt;
min_T = float(input(&amp;quot;Enter min_T: &amp;quot;)) #prompts user to input lower limit of temperature range&lt;br /&gt;
max_T = float(input(&amp;quot;Enter max_T: &amp;quot;)) #prompts user to input upper limit of temperature range&lt;br /&gt;
data_from_C=np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
T=data_from_C[:, 0]&lt;br /&gt;
C=data_from_C[:, 5]&lt;br /&gt;
colour=[&amp;quot;000000&amp;quot;, &amp;quot;009900&amp;quot;, &amp;quot;330000&amp;quot;,  &amp;quot;669900&amp;quot;, &amp;quot;996600&amp;quot;, &amp;quot;FF00FF&amp;quot;,  &amp;quot;CCFFFF&amp;quot;] #list of various colours of plot lines in hexadecimal code&lt;br /&gt;
c=0 #indexes hexidecimal colour codes in colour within the for loop&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
hcax = fig.add_subplot(1,1,1)&lt;br /&gt;
hcax.set_ylabel(&amp;quot;Heat capacity/spin&amp;quot;)&lt;br /&gt;
hcax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
lower_limit=int(input(&amp;quot;Enter lower limit: &amp;quot;)) #prompts user to input smallest polynomial degree&lt;br /&gt;
deg_list=range(lower_limit,(lower_limit+7)) #generates list of 6 polynomial degrees starting from lower_limit&lt;br /&gt;
#the following lines fit the C_++ simulation data to each polynomial degree in deg_list and then plots the fitted curves and original data&lt;br /&gt;
for deg in deg_list:&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    hcax.plot(T_range, fitted_C_values, &amp;quot;#&amp;quot;+colour[c], label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot; (fitted data n={})&amp;quot;.format(deg))&lt;br /&gt;
    c+=1&lt;br /&gt;
hcax.plot(T, C, color=&amp;quot;m&amp;quot;, label=str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows))&lt;br /&gt;
hcax.legend(loc=2,prop={&#039;size&#039;:6})&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:IL_2x2_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_4x4_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_8x8_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
[[File:IL_16x16_polyfit_regionRM1412.png|thumb|700px|center]][[File:IL_32x32_polyfit_regionRM1412.png|thumb|700px|center]]&lt;br /&gt;
&#039;&#039;&#039;Task 19&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from numpy import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
&lt;br /&gt;
import numpy as np&lt;br /&gt;
n_rows=2&lt;br /&gt;
list_n_rows=[]&lt;br /&gt;
min_T = 2 #&lt;br /&gt;
max_T = 3 #&lt;br /&gt;
T_max_values = []&lt;br /&gt;
deg = 8 #&lt;br /&gt;
while n_rows&amp;lt;=32:&lt;br /&gt;
    data_from_C = np.loadtxt(&amp;quot;../ImperialChemYear3CMPExptmaster/C++_data/&amp;quot;+str(n_rows)+&amp;quot;x&amp;quot;+str(n_rows)+&amp;quot;.dat&amp;quot;)&lt;br /&gt;
    T = data_from_C[:, 0]&lt;br /&gt;
    C = data_from_C[:, 5]&lt;br /&gt;
    selection = np.logical_and(T &amp;gt; min_T, T &amp;lt; max_T)&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, deg)&lt;br /&gt;
    T_range = np.linspace(min_T, max_T, 1000) &lt;br /&gt;
    fitted_C_values = np.polyval(fit, T_range)&lt;br /&gt;
    C_max = np.max(fitted_C_values)&lt;br /&gt;
    T_max = T_range[fitted_C_values == C_max]&lt;br /&gt;
    list_n_rows.append(n_rows)&lt;br /&gt;
    n_rows=2*n_rows&lt;br /&gt;
    T_max_values.append(T_max)&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((list_n_rows, T_max_values))&lt;br /&gt;
np.savetxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(deg), final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
from numpy import *&lt;br /&gt;
import numpy as np&lt;br /&gt;
data=np.loadtxt(&amp;quot;T_c_determination_data_deg_{}.dat&amp;quot;.format(8))&lt;br /&gt;
L=data[ : , 0]&lt;br /&gt;
T=data[ : , 1]&lt;br /&gt;
fit = np.polyfit(1/L, T, 1)&lt;br /&gt;
upp_lim=np.max(1/L)&lt;br /&gt;
inverse_L_range=np.linspace(0, upp_lim, 1000)&lt;br /&gt;
fitted = np.polyval(fit, inverse_L_range)&lt;br /&gt;
settings = dict(boxstyle=&#039;square&#039;, facecolor=&#039;white&#039;, alpha=0.5)&lt;br /&gt;
text_string=(&amp;quot;A= {}&amp;quot;.format(fit[0]), &amp;quot;T_c= {}&amp;quot;.format(fit[1]))&lt;br /&gt;
fig = pl.figure()&lt;br /&gt;
ax = fig.add_subplot(1,1,1)&lt;br /&gt;
ax.set_ylabel(&amp;quot;Peak temperature (K)&amp;quot;)&lt;br /&gt;
ax.set_xlabel(&amp;quot;1/L&amp;quot;)&lt;br /&gt;
ax.plot(1/L, T, &amp;quot;r&amp;quot;, linestyle=&amp;quot;&amp;quot;, marker=&amp;quot;o&amp;quot;)&lt;br /&gt;
ax.text(0.01, 0.95, text_string, transform=ax.transAxes, fontsize=14, verticalalignment=&#039;top&#039;, bbox=settings)&lt;br /&gt;
ax.plot(inverse_L_range, fitted, &amp;quot;b&amp;quot;)&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:ILT_c_vs_invLRM1412.png|thumb|700px|center]]&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Rm1412</name></author>
	</entry>
</feed>