<?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=Mj1516</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=Mj1516"/>
	<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/wiki/Special:Contributions/Mj1516"/>
	<updated>2026-06-10T18:42:52Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.8</generator>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813454</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813454"/>
		<updated>2020-12-10T19:35:16Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
The final version of the code is available as: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py&lt;br /&gt;
&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Animation of code is done in https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/ILanim.py&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtimetrial.py to record how long your current version of IsingLattice.py: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py from https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813453</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813453"/>
		<updated>2020-12-10T19:33:01Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Accelerating the code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
The final version of the code is available as: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py&lt;br /&gt;
&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Animation of code is done in https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/ILanim.py&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtimetrial.py to record how long your current version of IsingLattice.py: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py from https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813452</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813452"/>
		<updated>2020-12-10T19:30:38Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
The final version of the code is available as: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py&lt;br /&gt;
&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Animation of code is done in https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/ILanim.py&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py from https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813451</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813451"/>
		<updated>2020-12-10T19:27:58Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Running over a range of temperatures */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
The final version of the code is available as: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py&lt;br /&gt;
&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py from https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813450</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813450"/>
		<updated>2020-12-10T19:25:48Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Calculating the energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
The final version of the code is available as: https://github.com/PanoptoSalad/Monte_Carlo_Ising_lattice/blob/main/IsingLattice_jiang.py&lt;br /&gt;
&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813449</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813449"/>
		<updated>2020-12-10T19:15:07Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_cols) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813448</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813448"/>
		<updated>2020-12-10T18:35:41Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Calculating the energy and Magnetisation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
We can define our ising lattice as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E = 0.0&lt;br /&gt;
    E2 = 0.0&lt;br /&gt;
    M = 0.0&lt;br /&gt;
    M2 = 0.0&lt;br /&gt;
&lt;br /&gt;
    n_cycles = 0&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice([-1,1], size=(n_rows, n_cols))&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_rows) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813447</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=813447"/>
		<updated>2020-12-10T18:32:30Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / self.n_rows) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796685</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796685"/>
		<updated>2019-11-20T11:59:53Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extension: External magnetic Field */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
An additional argument f can be used for external field&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self,F):&lt;br /&gt;
        L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 1), self.lattice))&lt;br /&gt;
        R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
            np.multiply(np.roll(self.lattice, 1, 0), self.lattice))&lt;br /&gt;
	M = F * self.lattice&lt;br /&gt;
        return - L - R - M  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796682</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796682"/>
		<updated>2019-11-20T11:58:04Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensions: Spinodal Decomposition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Extension: External magnetic Field ==&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796681</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796681"/>
		<updated>2019-11-20T11:57:17Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Results */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number 1 million]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796680</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796680"/>
		<updated>2019-11-20T11:56:56Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensions: Spinodal Decomposition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 10 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796678</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796678"/>
		<updated>2019-11-20T11:56:31Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensions: Spinodal Decomposition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg|thumb|center|500px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        lat_old = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_j1 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i1 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j2 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i2 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
        random_j3 = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        random_i3 = np.random.choice(range(0, self.n_rows))&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i1][random_j1] *=  -1 # random spin flipped&lt;br /&gt;
        self.lattice[random_i2][random_j2] *= -1  # random spin flipped&lt;br /&gt;
        self.lattice[random_i3][random_j3] *= -1  # random spin flipped&lt;br /&gt;
&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice = lat_old&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&lt;br /&gt;
        return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796675</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796675"/>
		<updated>2019-11-20T11:55:28Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensions: Spinodal Decomposition */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW2_1.svg|thumb|center|1000px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
When letting the simulation run at the critical temperature, spinodal decomposition can be seen, due to the curved magnetic domains.&lt;br /&gt;
&lt;br /&gt;
[[File:SpinodalcharlieBROWNWWNNW4flips.svg.|thumb|center|1000px|Illustration of the evolution of the 100 x 100 ising lattice at 2.26 temperature units over 1 million cycles]]&lt;br /&gt;
&lt;br /&gt;
To speed up equilibration, 4 random lattice sites were chosen and flipped simultaneously using the modified code below.&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW.svg&amp;diff=796669</id>
		<title>File:SpinodalcharlieBROWNWWNNW.svg</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW.svg&amp;diff=796669"/>
		<updated>2019-11-20T11:51:14Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW2_1.svg&amp;diff=796668</id>
		<title>File:SpinodalcharlieBROWNWWNNW2 1.svg</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW2_1.svg&amp;diff=796668"/>
		<updated>2019-11-20T11:51:11Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW4flips.svg&amp;diff=796667</id>
		<title>File:SpinodalcharlieBROWNWWNNW4flips.svg</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:SpinodalcharlieBROWNWWNNW4flips.svg&amp;diff=796667"/>
		<updated>2019-11-20T11:51:07Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796666</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796666"/>
		<updated>2019-11-20T11:49:34Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Extensions: Spinodal Decomposition ==&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796663</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796663"/>
		<updated>2019-11-20T11:48:35Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Animation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
This script takes in a .traj file produced earlier and animates it as an mp4 file&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796662</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796662"/>
		<updated>2019-11-20T11:48:09Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Implementing Monte Carlo Steps */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Animation ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as plt&lt;br /&gt;
from matplotlib import animation&lt;br /&gt;
import numpy as np&lt;br /&gt;
import re&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in trajectory file name (.traj): &#039;)&lt;br /&gt;
&lt;br /&gt;
first_line1 = open(filename,&#039;r&#039;)&lt;br /&gt;
first_line = first_line1.readlines()[0]&lt;br /&gt;
first_line1.close()&lt;br /&gt;
cycles,rows,cols =  re.findall(r&amp;quot;[\w&#039;]+&amp;quot;, first_line)[2:]&lt;br /&gt;
cycles = int(cycles)&lt;br /&gt;
rows = int(rows)&lt;br /&gt;
cols = int(cols)&lt;br /&gt;
&lt;br /&gt;
new_data = np.loadtxt(filename) &lt;br /&gt;
&lt;br /&gt;
# this produces a 2D array from original 3D array&lt;br /&gt;
# We can restore 3D array if we know the shape as stored in the comment earlier&lt;br /&gt;
&lt;br /&gt;
l_list = new_data.reshape(cycles, rows, cols)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure()&lt;br /&gt;
ax = plt.subplot(111)&lt;br /&gt;
&lt;br /&gt;
ax.xaxis.set_ticks([])&lt;br /&gt;
ax.yaxis.set_ticks([])&lt;br /&gt;
&lt;br /&gt;
line = ax.matshow(l_list[0], cmap=&#039;hsv&#039;, vmin=0, vmax=2*np.pi)&lt;br /&gt;
&lt;br /&gt;
def update(i):&lt;br /&gt;
    line.set_data(l_list[i])&lt;br /&gt;
    return line,&lt;br /&gt;
&lt;br /&gt;
anim = animation.FuncAnimation(fig, update,interval=0,save_count=cycles, blit=True)&lt;br /&gt;
plt.colorbar(line,label=&#039;Spin direction from &#039;+ r&#039;$0 - 2\pi$&#039;)&lt;br /&gt;
&lt;br /&gt;
anim.save(&#039;{}_latticetraj.mp4&#039;.format(filename.split(&#039;.&#039;)[0]), fps=30)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796661</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796661"/>
		<updated>2019-11-20T11:46:08Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}^{-1}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
Units of temperature:  (a) T = 0.1  (b) T = 1  (c) T = 2  (d) T = 3 ]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 : above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 . a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796660</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796660"/>
		<updated>2019-11-20T11:43:29Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Implementing Monte Carlo Steps */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&lt;br /&gt;
This script will read an input file containing information regarding the lattice size, temperature and number of cycles and produce 2 output files showing the simulation behaviour every cycle. The name of the file will be the same for output and inputs. Only the extension changes.&lt;br /&gt;
&lt;br /&gt;
Input file: [[file:Input1.in]]&lt;br /&gt;
Output data file: [[file:Input1.dat]] and [[file:Input1.traj]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.traj&amp;diff=796659</id>
		<title>File:Input1.traj</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.traj&amp;diff=796659"/>
		<updated>2019-11-20T11:43:16Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.dat&amp;diff=796658</id>
		<title>File:Input1.dat</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.dat&amp;diff=796658"/>
		<updated>2019-11-20T11:42:03Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796655</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796655"/>
		<updated>2019-11-20T11:39:19Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Implementing Monte Carlo Steps */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, energy as an .dat file. Also produces a trajectory file .traj illustrating the lattice site spins per cycle&lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796654</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796654"/>
		<updated>2019-11-20T11:38:30Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Background */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing Monte Carlo Steps ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Reads an input file, produces an output file with magnetism, &lt;br /&gt;
&lt;br /&gt;
from IsingLattice_magnet import *&lt;br /&gt;
&lt;br /&gt;
filename = input(&#039;Please put in file name: &#039;)&lt;br /&gt;
&lt;br /&gt;
f=open(&#039;{}&#039;.format(filename), &amp;quot;r&amp;quot;) # reads the input file&lt;br /&gt;
data = []&lt;br /&gt;
for i in f.readlines():&lt;br /&gt;
    data.append(i.split()[-1]) # reads the last string (seperated by &#039; &#039;) of each line&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
N = int(data[0]) # Lattice length&lt;br /&gt;
T = float(data[1]) # Temperature &lt;br /&gt;
cycles = int(data[2]) # Number of cycles implemented&lt;br /&gt;
filename = filename.split(&#039;.&#039;)[0] # Output file name&lt;br /&gt;
&lt;br /&gt;
il = IsingLattice(N,N) # create a 2D lattice of the class Isinglattice&lt;br /&gt;
spins = N*N&lt;br /&gt;
&lt;br /&gt;
e_list = [] # energy of the &lt;br /&gt;
mag_abs = [] # magnetism stored as a complex number, absolute value&lt;br /&gt;
mag_ang = [] # the angle of the complex number&lt;br /&gt;
l_list = [] # stores the lattice angles &lt;br /&gt;
&lt;br /&gt;
def animate(runs,temperature):&lt;br /&gt;
    &#039; Does a monte carlo step and appends various properties to list above &#039;&lt;br /&gt;
    for _ in range(runs):&lt;br /&gt;
        energy, magnetisation,shape = il.montecarlostep(temperature) # uses output defined in IsingLattice_magnet.py&lt;br /&gt;
        e_list.append(energy)&lt;br /&gt;
        mag_abs.append(magnetisation[0])&lt;br /&gt;
        mag_ang.append(magnetisation[1])&lt;br /&gt;
        l_list.append(shape)        &lt;br /&gt;
&lt;br /&gt;
animate(cycles,T) # Run the monte carlo simulation over a certain number of cycles&lt;br /&gt;
&lt;br /&gt;
# From this point onwards code stores the data produced&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((e_list,mag_abs,mag_ang))&lt;br /&gt;
np.savetxt(&amp;quot;{}.dat&amp;quot;.format(filename), final_data) # storing the net magnetisation, energy data&lt;br /&gt;
with open(&#039;{}.dat&#039;.format(filename), &#039;a&#039;) as outfile:&lt;br /&gt;
	outfile.write(&#039;# {} {} {} &#039;.format(N,T,cycles))&lt;br /&gt;
&lt;br /&gt;
# Deals with storing the lattice site data at each cycle. &lt;br /&gt;
# Lattice data has a shape of Number of cycles x number of rows x number of columns. 3D array.&lt;br /&gt;
&lt;br /&gt;
data = np.array(l_list)&lt;br /&gt;
&lt;br /&gt;
with open(&#039;{}.traj&#039;.format(filename), &#039;w&#039;) as outfile:&lt;br /&gt;
    outfile.write(&#039;# Array shape: {0}\n&#039;.format(data.shape)) # creates a header with information about data array&lt;br /&gt;
    &lt;br /&gt;
    for data_slice in data:&lt;br /&gt;
        np.savetxt(outfile, data_slice) # Stores the lattice data at each cycle&lt;br /&gt;
        outfile.write(&#039;# New cycle\n&#039;) # new cycle comment between each cycle&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796653</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796653"/>
		<updated>2019-11-20T11:36:48Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Results */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 10000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796652</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796652"/>
		<updated>2019-11-20T11:36:10Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
=== Background ===&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Results ===&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region. This is achieved at temperatures below the critical point.&lt;br /&gt;
&lt;br /&gt;
When the temperature is raised above the critical point at 2.5 temperature units, the equilibrium configuration favours a random distribution instead.&lt;br /&gt;
&lt;br /&gt;
[[File:8_25critjmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=nS7wBZSP9d4&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 2.5 temperature units]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:8_25critjmx.PNG&amp;diff=796650</id>
		<title>File:8 25critjmx.PNG</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:8_25critjmx.PNG&amp;diff=796650"/>
		<updated>2019-11-20T11:34:51Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796648</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796648"/>
		<updated>2019-11-20T11:30:47Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Start3jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Start3jmx.PNG&amp;diff=796647</id>
		<title>File:Start3jmx.PNG</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Start3jmx.PNG&amp;diff=796647"/>
		<updated>2019-11-20T11:30:26Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796646</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796646"/>
		<updated>2019-11-20T11:30:03Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the 32 x 32 XY model lattice at 0.1 temperature units simulation with cycle number]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796645</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796645"/>
		<updated>2019-11-20T11:29:30Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|1000px|Illustration of the evolution of the simulation with time]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796644</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796644"/>
		<updated>2019-11-20T11:29:16Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
[[File:Jmxplsworksdasdas.PNG|thumb|center|500px|Illustration of the evolution of the simulation with time]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Jmxplsworksdasdas.PNG&amp;diff=796643</id>
		<title>File:Jmxplsworksdasdas.PNG</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Jmxplsworksdasdas.PNG&amp;diff=796643"/>
		<updated>2019-11-20T11:28:34Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796630</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796630"/>
		<updated>2019-11-20T11:20:56Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
The simulation was then run for 500,000 cycles for a 32 x 32 system, and the video of the animation is as shown:&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=HDdifkqIMhM &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the approximately 250,000 cycles of a 32 x 32 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796629</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796629"/>
		<updated>2019-11-20T11:18:08Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;br /&gt;
&lt;br /&gt;
== Bibliography ==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796628</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796628"/>
		<updated>2019-11-20T11:17:31Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796627</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796627"/>
		<updated>2019-11-20T11:17:13Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
For an 8 x 8, equilibration is achieved when the colours of the lattice sites cluster around a particular region.&lt;br /&gt;
&lt;br /&gt;
[[File:Round disk jmx.PNG.PNG|thumb|center|340px|https://www.youtube.com/watch?v=F7eG4cVyPVI&amp;amp;feature=youtu.be &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the magnetisation trajectory of the first 10000 steps of a 8 x 8 XY model lattice at 0.1 temperature units]]&lt;br /&gt;
&lt;br /&gt;
The magnetisation vector trajectory for the lattice spin can be visualised using a polar plot to show the magnitude and direction per spin. Initially, the net magnetisation magnitude is 0. It slowly increases with time to approach 1, showing that the spins are now all aligned. The starting net direction of the spin affects the final net direction of the spins.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.in&amp;diff=796624</id>
		<title>File:Input1.in</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Input1.in&amp;diff=796624"/>
		<updated>2019-11-20T11:11:53Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=File:Round_disk_jmx.PNG&amp;diff=796623</id>
		<title>File:Round disk jmx.PNG</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=File:Round_disk_jmx.PNG&amp;diff=796623"/>
		<updated>2019-11-20T11:11:52Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796619</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796619"/>
		<updated>2019-11-20T11:05:20Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Potts Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model q is allowed to go to ∞, the spins can adopt any spin direction θ, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π. This reflects the direction of the spin.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude. A dot product will have to be incorporated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H =-J\sum_{(i, j)} \mathbf{s}_{i} \cdot \mathbf{s}_{j}-\sum_{j} \mathbf{h}_{j} \cdot \mathbf{s}_{j} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the XY model, the magnitude of the spins is set to 1. In this section, the external field was treated to be zero. This simplifies the expression as energy is now a function of the cosine of the difference in angle between neighbouring sites. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=-J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The magnetisation is now a vector sum and rather than a simple scalar addition. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The Monte Carlo algorithm is also modified to pick a random number between 0 to 2π to perturb a current lattice site&#039;s spin by rather than just flipping a spin by -1&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796613</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796613"/>
		<updated>2019-11-20T10:55:34Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Extensionsː Hsienberg Model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Potts Model ==&lt;br /&gt;
&lt;br /&gt;
The Potts&#039; model allows the spin to adopt a q number of configurations about a circle. In the Ising model, q = 2. The angles permitted are:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math. \theta_{n}=\frac{2 \pi n}{q} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the continuous spin model, the spins can adopt any configuration, instead of just +1 or -1. This is reflected in the following code when each lattice site is assigned a random number between 0 to 2 π.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for Potts Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once this restriction on spin has been lifted, the Hamiltonian will differ. This is because the spin is a 2D vector with direction and magnitude.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt; H=J \sum_{(i, j)} \cos \left(\theta_{s_{i}}-\theta_{s_{j}}\right) &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796606</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796606"/>
		<updated>2019-11-20T10:44:39Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* The model */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796591</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796591"/>
		<updated>2019-11-20T10:36:10Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. &amp;lt;ref&amp;gt;   L. Knox, N. Christensen and C. Skordis,The Astrophysical JournalLetters, 2001,563, L95. &amp;lt;/ref&amp;gt; It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796586</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796586"/>
		<updated>2019-11-20T10:34:24Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt; asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796583</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796583"/>
		<updated>2019-11-20T10:33:14Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &amp;lt;ref&amp;gt;  B. M. McCoy and T. T. Wu,The two-dimensional Ising model, CourierCorporation, 2014. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796579</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796579"/>
		<updated>2019-11-20T10:30:55Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of numpy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796576</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796576"/>
		<updated>2019-11-20T10:30:28Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Task 3 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of NumPy has sped up the calculations drastically. This is because of the way numpy stores its information in an array manner, and also because it is a library written in C++ code. &amp;lt;ref&amp;gt; S. Van Der Walt, S. C. Colbert, and G. Varoquaux, Computing in Science &amp;amp; Engineering, 2011,13, 22. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
	<entry>
		<id>https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796571</id>
		<title>Rep:CMP 01180512 y3</title>
		<link rel="alternate" type="text/html" href="https://chemwiki.ch.ic.ac.uk/index.php?title=Rep:CMP_01180512_y3&amp;diff=796571"/>
		<updated>2019-11-20T10:28:32Z</updated>

		<summary type="html">&lt;p&gt;Mj1516: /* Correcting the Averaging Code */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction to the Ising model ==&lt;br /&gt;
&lt;br /&gt;
The Ising model consists of a rectangular lattice made up of discrete lattice sites with either +1 or -1 spins. &lt;br /&gt;
These lattice sites experience periodic boundary conditions and interact with their immediate neighbours, in terms of exchange energy J. In a ferromagnetic model, adjacent parallel spins have favourable exchange energy while neighbouring antiparallel spins result in a higher energy state. This deceptively simple model should not be underestimated, due to its ability to predict phase transitions and thermodynamic properties of various phases of magnets. For instance, the beta-brass alloy can be modeled well with the Ising model, and the theoretical order-disorder transitions match experimental data obtained from x-ray scattering. More interestingly, the Ising model is used in neuroscience by Schneidman et. Al. &amp;lt;ref&amp;gt; E. Schneidman, M. J. Berry II, R. Segev and W. Bialek,Nature, 2006,440, 1007 &amp;lt;/ref&amp;gt; to describe neuron activity as +1 (active) or -1 (inactive). The neuron activity of one axon influences the activity of another, sending a cascade of electrical signals in the brain.&lt;br /&gt;
&lt;br /&gt;
In 1924, Lenz gave his student Ising the above model to solve &amp;lt;ref&amp;gt; E. Ising, Z. Phys., 1925,31, 253–258. &amp;lt;/ref&amp;gt;, and Ising solved it for a 1D system. In 1944, Onsager provided a beautiful derivation &amp;lt;ref&amp;gt; S. M. Bhattacharjee and A. Khare,Current science, 1995,69, 816–821 &amp;lt;/ref&amp;gt; for the 2D Ising model&#039;s partition function, which unlocked insights into the thermodynamic behaviour of the lattice. In this work, the 2D ising model would be simulated numerically using the Monte Carlo algorithm, and the various properties that emerge would be accounted for and compared to theoretical and literature results.&lt;br /&gt;
===The model===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. Show that the lowest possible energy for the Ising model is &amp;lt;math&amp;gt;E\ =\ -DNJ&amp;lt;/math&amp;gt; where &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; is the number of dimensions and &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; is the total number of spins.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The Hamiltonian for the 2D Ising lattice is given byː &amp;lt;ref&amp;gt; J. M. Yeomans, Statistical mechanics of phase transitions, ClarendonPress, 1992 &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;&lt;br /&gt;
\mathcal{H}=-\frac{1}{2} J \sum_{i}^{N} \sum_{j \in \text { neighbours }(i)} s_{i} s_{j}-E \sum_{i} s_{i}&lt;br /&gt;
&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where E is the external magnetic field that can point up or down and possess a magnitude. This is relevant in the context of Nuclear Magnetic Resonance (NMR) studies where spins interact with external magnetic fields. In fact, the analytical solution for the non zero-field Ising model has only been cracked by Wilson in 1982 (who received a nobel prize for his hard work). For the rest of the studies, a zero-field model is assumed for the sake of simplicity.   &lt;br /&gt;
&lt;br /&gt;
The total energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; of the lattice in the absence of an external field is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the spins are aligned, &amp;lt;math&amp;gt;s_i s_j = +1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=-J&amp;lt;/math&amp;gt;, if they are antiparallel, &amp;lt;math&amp;gt;s_i s_j = -1&amp;lt;/math&amp;gt; and &amp;lt;math&amp;gt;E=+J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|thumb|center|Illustrations of 1D, 2D and 3D lattices with lattice sites of interest labeled.]]&lt;br /&gt;
&lt;br /&gt;
Looking at the 1D case with &amp;lt;math&amp;gt;N=5&amp;lt;/math&amp;gt;, each lattice site interacts two times, with its left and right neighbours.&lt;br /&gt;
&lt;br /&gt;
Assuming that all spins are aligned in the following calculations: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E&amp;amp;=- J\left(s_{1} s_{5}+s_{1} s_{2}+s_{2} s_{3}+s_{3} s_{4}+s_{4} s_{5}\right) \\&lt;br /&gt;
&amp;amp;=-\frac{1}{2} J\left(s_{1} s_{5}+s_{1} s_{2}\right) \times 5&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that if each atom is treated as having &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt; interactions, one might expect there to be &amp;lt;math&amp;gt;5\times2&amp;lt;/math&amp;gt; for 5 lattice points. However, this would be double counting, since two neighbouring atoms have the same interactions with each other. A factor of &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; is introduced to compensate.&lt;br /&gt;
&lt;br /&gt;
Looking at the 2D case with &amp;lt;math&amp;gt;N=25&amp;lt;/math&amp;gt;, each lattice site interacts four times, with its left, right, top and bottom neighbours&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E=-\frac{1}{2} J\left(s_{1} s_{2}+s_{1} s_{6}+s_{1} s_{25}+s_{1} s_{25}\right) \times 25&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Looking at the &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt; Dimensional case with &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; atoms, each lattice site interacts &amp;lt;math&amp;gt;2\times D&amp;lt;/math&amp;gt; times with its immediate neighbours. &amp;lt;math&amp;gt;0.5&amp;lt;/math&amp;gt; avoids double counting. The lowest possible energy &amp;lt;math&amp;gt;E&amp;lt;/math&amp;gt; occurs when all spins are aligned: &lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} E &amp;amp;=-\frac{1}{2} J(1 \times 2 D) \times N \\ &lt;br /&gt;
&amp;amp;=-D N J \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What is the multiplicity of this state?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The multiplicity of this gound state can be represented with &amp;lt;math&amp;gt;\Omega&amp;lt;/math&amp;gt;. Since all the spin states are aligned, there are only two possible states: either all lattice points spin up or all spin down. These two states are degenerate in energy.&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Omega=2&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;C. Calculate its entropy.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Entropy &amp;lt;math&amp;gt;S&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
 &lt;br /&gt;
:&amp;lt;math&amp;gt; \begin{align} S &amp;amp;=-k_{B} \ln \Omega \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math&amp;gt;k_{B}&amp;lt;/math&amp;gt; is the Boltzmann constant. &lt;br /&gt;
&lt;br /&gt;
For &amp;lt;math&amp;gt;\Omega=2&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align} S &amp;amp;=k_{B} \ln 2 \end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
===Phase Transitions===&lt;br /&gt;
==== Task 2 ====&lt;br /&gt;
&#039;&#039;&#039;A. Imagine that the system is in the lowest energy configuration. To move to a different state, one of the spins must spontaneously change direction (&amp;quot;flip&amp;quot;). What is the change in energy if this happens (&amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt;)?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The ground state for state of &amp;lt;math&amp;gt;D=3,\ N=1000&amp;lt;/math&amp;gt; is given by &amp;lt;math&amp;gt;E_{0}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E &amp;amp;= - \frac{1}{2} J \sum_i^N \sum_{j\  \in\  \mathrm{neighbours}\left(i\right)} s_i s_j \\&lt;br /&gt;
E_{0} &amp;amp;= -\frac{1}{2} J(1 \times 6) \times 1000 \\&lt;br /&gt;
&amp;amp;= -3000 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If one state is flipped, there would be 6 formerly negative (energetically favouarble) &amp;lt;math&amp;gt;s_i s_j&amp;lt;/math&amp;gt; interactions becoming positive energy interactions instead. The energy would change from &amp;lt;math&amp;gt; - 6 J &amp;lt;/math&amp;gt; to &amp;lt;math&amp;gt; + 6 J &amp;lt;/math&amp;gt;, representing an increasing of &amp;lt;math&amp;gt;12 J&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The new energy is given by:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
E_{1\ flip} &amp;amp;= -3000 J + 12 J \\&lt;br /&gt;
&amp;amp;= -2988 J&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How much entropy does the system gain by doing so?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If all spins in the lattice were initially +1, then the flip to -1 could happen for any particular lattice site out of the thousand. There are 1000 possible states with energetic degeneracy. If all spins were initially -1 instead, a flip will create another 1000 possible states. So in total, the number of microstates increased to &amp;lt;math&amp;gt;\Omega = 2000&amp;lt;/math&amp;gt; from &amp;lt;math&amp;gt;\Omega = 2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The change of entropy &amp;lt;math&amp;gt;=\Delta S&amp;lt;/math&amp;gt; is given by:&lt;br /&gt;
:&amp;lt;math&amp;gt;\begin{align}&lt;br /&gt;
\Delta S &amp;amp;=k_{B} \ln \left(\Omega_{\text {new }} / \Omega_{\text {old }}\right) \\ &amp;amp;=k_{B} \ln \frac{2000}{2} \\ &amp;amp;=k_{B} \ln 1000&lt;br /&gt;
\end{align}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 3 ====&lt;br /&gt;
&#039;&#039;&#039;A. Calculate the magnetisation of the 1D and 2D lattices in the figure below&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Cellsjmx.PNG|500px]]&lt;br /&gt;
&lt;br /&gt;
The total magnetisation of the system &amp;lt;math&amp;gt;M&amp;lt;/math&amp;gt; is defined as:&lt;br /&gt;
:&amp;lt;math&amp;gt;M=\sum_{i} s_{i}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 1 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=3-2=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a 2 D lattice:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=13-12=1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. What magnetisation would you expect to observe for an Ising lattice with &amp;lt;math&amp;gt;D = 3,\ N=1000&amp;lt;/math&amp;gt; at absolute zero?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For 1000 spins, at 0 K, the Ising lattice would be at its ground state. This is because: &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; F = U - TS &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When T=0 K, the free energy is determined solely by the exchange energy, which is at its lowest when all spins are aligned. The energy is lowest when the magnetisation is at its highest:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M=1000&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Calculating the energy and Magnetisation ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Complete the functions energy() and magnetisation(), which should return the energy of the lattice and the total magnetisation, respectively. In the energy() function you may assume that &amp;lt;math&amp;gt;J=1.0&amp;lt;/math&amp;gt; at all times.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    &amp;quot;Return the total energy of the current lattice configuration.&amp;quot;&lt;br /&gt;
    N = self.n_cols * self.n_rows # Total number of spin states&lt;br /&gt;
    energy = 0.0&lt;br /&gt;
    for i in range(N):&lt;br /&gt;
        col = int(i % self.n_cols) # generate cols and rows with 1 for loop&lt;br /&gt;
        row = int((i - col) / 5) &lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row][col - 1] # interactions with left&lt;br /&gt;
        energy += self.lattice[row][col] * self.lattice[row - 1][col] # interactions with up&lt;br /&gt;
    return -energy&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def magnetisation(self):&lt;br /&gt;
    &amp;quot;Return the total magnetisation of the current lattice configuration.&amp;quot;&lt;br /&gt;
    magnetisation = np.sum(self.lattice) # adds up all the spin values in the lattice&lt;br /&gt;
    return magnetisation&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Run the ILcheck.py script from the IPython Qt console using the command&#039;&#039;&#039;&lt;br /&gt;
[[File:Jmx_checkipy.png|thumb|center|Results from the ILcheck.py script. The expected Energy, E and magnetisation, M calculated from the code written previously in this work matched values of the script]]&lt;br /&gt;
&lt;br /&gt;
== Introduction to the Monte Carlo simulation ==&lt;br /&gt;
&lt;br /&gt;
Introduced in 1953, the Monte Carlo approach is a computational tour de force devised by Metropolis et Al. It is a stochastic method that aims to locate global minima by providing probabilistic perturbations to probe the energy surface. This allows the simulation to achieve ergodicity by overcoming potential barriers (provided temperature is high enough) and sample the whole configuration space of the lattice. It is a Markov chain process, whose future state depends entirely on its current state. &amp;lt;ref&amp;gt; K. Binder, D. Heermann, L. Roelofs, A. J. Mallinckrodt and S. McKay, Computers in Physics, 1993,7, 156–157. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Montecarllologic.PNG|thumb|450px|center|Algorithimic logic flow chart of Monte Carlo method to simulate Ising Lattice for a number of cycles]]&lt;br /&gt;
===Average Energy and Magnetisation===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. How many configurations are available to a system with 100 spins?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each cell has 2 possible spin states, so with 100 cells, there are&lt;br /&gt;
:&amp;lt;math&amp;gt; 2^{N} = 2^{100} &amp;lt;/math&amp;gt;&lt;br /&gt;
possible configurations.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. To evaluate these expressions, we have to calculate the energy and magnetisation for each of these configurations, then perform the sum. Let&#039;s be very, very, generous, and say that we can analyse &amp;lt;math&amp;gt;1\times 10^9&amp;lt;/math&amp;gt; configurations per second with our computer. How long will it take to evaluate a single value of &amp;lt;math&amp;gt;\left\langle M\right\rangle_T&amp;lt;/math&amp;gt;?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The calculation time is:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} 2^{100} \times 10^{-9} &amp;amp;=1.27 \times 10^{21} \text { seconds } \\ &amp;amp;=4.02 \times 10^{13} \text { years } \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
In comparison, the universe is only &amp;lt;math&amp;gt;1.38 \times 10^{10}&amp;lt;/math&amp;gt; years old. It is clear that considering all possible energy configurations is not a viable strategy. Fortunately, most of the high energy configurations are not very probable according to the Boltzmann distribution. Hence, they contribute little to the partition function and the averaged properties of energy and magnetisation. The Monte Carlo algorithm embodies this by incorporating the Boltzmann distribution into its decision making of whether to spend computational power to consider high energy states. This method of importance sampling skips unnecessary calculations for improbable states. &amp;lt;ref&amp;gt; R. Y. Rubinstein and D. P. Kroese, Simulation and the Monte Carlo method, John Wiley &amp;amp; Sons, 2016, vol. 10. &amp;lt;/ref&amp;gt;&lt;br /&gt;
===Modifying IsingLattice.py===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Implement a single cycle of the above algorithm in the montecarlocycle(T) function. This function should return the energy of your lattice and the magnetisation at the end of the cycle. You may assume that the energy returned by your energy() function is in units of &amp;lt;math&amp;gt;k_B&amp;lt;/math&amp;gt;! Complete the statistics() function. This should return the following quantities whenever it is called: &amp;lt;math&amp;gt;&amp;lt;E&amp;gt;, &amp;lt;E^2&amp;gt;, &amp;lt;M&amp;gt;, &amp;lt;M^2&amp;gt;&amp;lt;/math&amp;gt;, and the number of Monte Carlo steps that have elapsed.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def montecarlostep(self, T):&lt;br /&gt;
    &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
    e_old = self.energy()&lt;br /&gt;
    random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&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] # random spin flipped&lt;br /&gt;
    if self.energy() &amp;gt; e_old:&lt;br /&gt;
        Del_E = self.energy() - e_old&lt;br /&gt;
        p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
        random_number = np.random.random() # choose a random number in the range [0,1) # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
        if random_number &amp;gt; p:&lt;br /&gt;
            self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j] # Monte Carlo Perturbation&lt;br /&gt;
&lt;br /&gt;
    self.E  += self.energy() # Values of energy and magnetisation added to the self.properties&lt;br /&gt;
    self.E2 += self.energy() ** 2&lt;br /&gt;
    self.M += self.magnetisation()&lt;br /&gt;
    self.M2 += self.magnetisation() ** 2&lt;br /&gt;
    self.n_cycles += 1&lt;br /&gt;
&lt;br /&gt;
    return self.energy(), self.magnetisation()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def statistics(self):&lt;br /&gt;
    &amp;quot;Return average values for various properties&amp;quot;&lt;br /&gt;
    return [self.E/self.n_cycles, self.E2/self.n_cycles, self.M/self.n_cycles, self.M2/self.n_cycles]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Animation of Monte Carlo Algorithim&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; NOTE, ALL VALUES OF TEMPERATURE IN THE WORK ARE REPORTED IN UNITS OF &amp;lt;math &amp;gt;J k_{B}&amp;lt;/math &amp;gt; NOT KELVINǃ &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If &amp;lt;math&amp;gt;T &amp;lt; T_C&amp;lt;/math&amp;gt;, do you expect a spontaneous magnetisation (i.e. do you expect &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;)? When the state of the simulation appears to stop changing (when you have reached an equilibrium state), use the controls to export the output to PNG and attach this to your report. You should also include the output from your statistics() function.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_C&amp;lt;/math&amp;gt; refers to the Curie temperature or critical temperature during phase transition. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is the average spin state of the configuration. &amp;lt;math&amp;gt;\left\langle M\right\rangle&amp;lt;/math&amp;gt; is an order parameter that is 0 beyond the critical point, but &amp;lt;math&amp;gt;\pm 1&amp;lt;/math&amp;gt; below it.&lt;br /&gt;
&lt;br /&gt;
[[File:4_temperatures_JMX2.jpg|thumb|center|600px|Plots of average energy and magentisation with cycle number for&lt;br /&gt;
 (a) T = 0.1 K (b) T = 1 K (c) T = 2 K (d) T = 3 K]]&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \Delta F=\Delta U-T \Delta S &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this simulation, a canonical NVT ensemble is used since the lattice volume, the number of particles and temperature in the Monte Carlo simulation are fixed. The Helmholtz free energy &amp;lt;math &amp;gt; F &amp;lt;/math &amp;gt; is used because the simulation is done at constant volume.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Above Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 3 K: above critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-112.27037777777778, 13021.761528888888, -12.429988148148148, 3238.0332325925924]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, &lt;br /&gt;
&lt;br /&gt;
Above the curie Temperature, at T = 3 K. a material loses its permanent magnetic properties because higher temperatures causes the entropic effects &amp;lt;math&amp;gt;\Delta S &amp;lt;/math&amp;gt; to become more significant, making configurations with unaligned spin distributions more probable and demagnetisation spontaneous. &amp;lt;math&amp;gt;\left\langle M\right\rangle = 0&amp;lt;/math&amp;gt; The magnetisation is no longer at +1 or -1 as seen at lower temperatures, but fluctuates around 0. This is also illustrated by the local clumps of magnetic domains that spring up from a breakdown of order at higher temperatures. These oppositedly aligned domains cancel each other out on average.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; At Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
At the critical temperature of 2.27 K, fractals patterns begin to emerge and are spinodal decomposition like. This effect is most present at larger lattice sizes. Smaller lattice sizes fail to capture this effect at this temperature because of finite size effects that distort the critical temperature (further elaboration later) and also due to the &#039;zoomed-in&#039; nature of the lattice that makes the patterns hard to identify.&lt;br /&gt;
&lt;br /&gt;
[[File:SPINODALATTEMPT JMX.PNG|thumb|center| Plot of 64 x 64 lattice at critical temperature]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 2 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-116.72018346666667, 13833.826653866667, -11.709477333333334, 3354.4296874666666]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.183&amp;lt;/math&amp;gt; due to spin flipping from the Monte carto algorithm identifying another energetically stable state of opposite spin.&lt;br /&gt;
&lt;br /&gt;
For T = 1 K: below critical temperature, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-120.33467282051282, 14670.371798974358, 55.758445860189106,  3233.2315876923076]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.871&amp;lt;/math&amp;gt;, since the simulate equilibrates around spins +1 or -1 lowest energy state. The deviation from -1 is due to the inclusion of simulation properties prior to equilibration.&lt;br /&gt;
 &lt;br /&gt;
Below the curie temperature, the stability from exchange energy J of aligned spin states results in a configuration where the majority of spin states point in parallel, so  &amp;lt;math&amp;gt;\left\langle M\right\rangle \neq 0&amp;lt;/math&amp;gt;.  Entropic effects do not dominate, and the thermodynamic instability from the increase in microstates from flipping a spin is outweighed by the much greater fall in stability from loss of exchange energies at lower temperatures. Spontaneous magnetisation can then occur. Note that the stochastic perturbation from the Monte Carlo algorithm allows the simulation to cross unstable configurations and &#039;flip&#039; spins, identifying 2 global minima in the process at T = 2 K. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Metastable states far below Critical Temperature &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
For T = 0.1 K: Metastable state, the statistic() function prints:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[-119.73041777777777 , 14535.222542222222, -6.757474444444444, 3162.881637777778]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Note that the &amp;lt;math&amp;gt;\left\langle M\right\rangle = -0.105&amp;lt;/math&amp;gt; due to metastable state formation.&lt;br /&gt;
&lt;br /&gt;
Note that metastable states (local minima) with 2 regions of oppositely aligned spins can possibly exist, making &amp;lt;math&amp;gt;\left\langle M\right\rangle \approx 0&amp;lt;/math&amp;gt; a possibility at very low temperatures like 0.1 K. These oppositely spin aligned magnetic domains are next to each other and share an edge. At very low temperatures, the likelihood of a higher energy spin configuration being accepted by the algorithm is low. Thus, the system rapidly finds a local minimum and gets trapped there. This prevents the simulation from overcoming any energy barriers since the Monte Carlo perturbation is so tiny. This causes the configuration to be potentially stuck in metastable states since attempts to leave the local minima well involve temporarily passing through higher energy configurations which will be rejected by the algorithm.&lt;br /&gt;
&lt;br /&gt;
== Accelerating the code ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your current version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the unoptimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 8.1367264&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 8.740262900000001&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 8.740708299999998&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 9.101113300000002&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 8.341777700000002&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 8.1021662&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 7.997737199999996&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 8.645950400000004&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 8.720095999999998&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 8.850238300000001&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 8.538 s&lt;br /&gt;
The standard deviation of time taken: 0.369 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 8.5 \pm 0.4 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;Look at the documentation for the NumPy sum function. You should be able to modify your magnetisation() function so that it uses this to evaluate M. The energy is a little trickier. Familiarise yourself with the NumPy roll and multiply functions, and use these to replace your energy double loop (you will need to call roll and multiply twice!).&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
def energy(self):&lt;br /&gt;
    L = np.sum( # Find energy of interactions with left neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 1), self.lattice))  &lt;br /&gt;
    R = np.sum(  # Find energy of interactions with right neighbour&lt;br /&gt;
        np.multiply(np.roll(self.lattice, 1, 0), self.lattice)) &lt;br /&gt;
    return - L - R  # add the energies together&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;Use the script ILtimetrial.py to record how long your new version of IsingLattice.py takes to perform 2000 Monte Carlo steps. This will vary, depending on what else the computer happens to be doing, so perform repeats and report the error in your average!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Using the optimised code:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Trial&lt;br /&gt;
! Time Taken&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0.38844220000009955&lt;br /&gt;
|-&lt;br /&gt;
| 2&lt;br /&gt;
| 0.36627260000000206&lt;br /&gt;
|-&lt;br /&gt;
| 3&lt;br /&gt;
| 0.401361100000031&lt;br /&gt;
|-&lt;br /&gt;
| 4&lt;br /&gt;
| 0.39879019999989396&lt;br /&gt;
|-&lt;br /&gt;
| 5&lt;br /&gt;
| 0.3895021999999244&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| 0.39292550000004667&lt;br /&gt;
|-&lt;br /&gt;
| 7&lt;br /&gt;
| 0.38721880000002784&lt;br /&gt;
|-&lt;br /&gt;
| 8&lt;br /&gt;
| 0.3867660999999316&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| 0.4128966999999193&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| 0.39296969999986686&lt;br /&gt;
|}&lt;br /&gt;
The mean of time taken: 0.391 s&lt;br /&gt;
The standard deviation of time taken: 0.0120 s&lt;br /&gt;
Hence:&lt;br /&gt;
:&amp;lt;math&amp;gt; time\ taken = 0.39 \pm 0.01 \ s&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents a 20 times improvement in the time taken for calculations. The substitution of python operations with that of NumPy has sped up the calculations drastically.&lt;br /&gt;
&lt;br /&gt;
== The effect of temperature ==&lt;br /&gt;
===Correcting the Averaging Code===&lt;br /&gt;
==== Task 1 ====&lt;br /&gt;
&#039;&#039;&#039;A. The script ILfinalframe.py runs for a given number of cycles at a given temperature, then plots a depiction of the final lattice state as well as graphs of the energy and magnetisation as a function of cycle number. This is much quicker than animating every frame! Experiment with different temperatures and lattice sizes. How many cycles are typically needed for the system to go from its random starting position to the equilibrium state?&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Lattice Size&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Size_jmx.png|thumb|center|1000px|Plots of energy and magentisation with step number for the various number of particles. Equilibration occurred more slowly for larger configurations]]&lt;br /&gt;
&lt;br /&gt;
Equilibration time increased significantly with grid size. This is because more spins are present, with only one spin being flipped at a time, more steps are needed to ‘flip’ the randomly distributed spins from one state to the other since the equilibrium state involves all spins being in the one configuration. To make matters worse, since the choice of which lattice site to &#039;flip&#039; is completely random, sites which are &#039;obviously&#039; unstable to the human (like a singular up spin surrounded by down spin) might be missed by the simulation altogetherǃ The Monte Carlo algorithm is like throwing darts randomly at a board with a blindfold on, so the larger the board, the harder it is to hit the sweet spot to flip the right spin. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For systems of larger grid sizes, flipping two or more spins simultaneously might be an attractive strategy to cut equilibration time (as shown in extension). &lt;br /&gt;
&lt;br /&gt;
This might not work for smaller size systems that suffer from finite-size effects since the two spins flipped will experience high correlation due to their close proximity from periodic boundary conditions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Equilibration and Temperature&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Various_temperatures_cutoffsjmx.png|thumb|center|800px|Plots of energy and magentisation with step number for various temperatures of simulations. Equilibration occurred faster for low temperatures, but equilibration time plateaus off beyond a certain temperature]]&lt;br /&gt;
&lt;br /&gt;
The system starts with an unoptimised random distribution of spins. When the temperature is lowered, the probability that a Boltzmann perturbation generates a higher energy state decreases, and the simulation is highly driven to rapidly find the most stable state, which is equilibrium when the magnetic spins are aligned. When the lattice size is kept constant, the system reaches equilibrium within a shorter number of cycles at lower temperatures than at higher temperatures. Beyond the critical temperature, however, the equilibrium has shifted to a lattice where long-range order has broken down and short-range local magnetic domains dominated. This is attained more easily from the initial random distribution since less order is present in the equilibrium state. This contrasts with the greater effort required to flip every spin to align in one direction at lower temperatures. At temperatures higher than the critical temperature, the equilibration time should stagnate or even decrease as the equilibrium position has shifted to a disorderly state that is easier to attain. &amp;lt;ref&amp;gt; J. M. Yeomans,Statistical mechanics of phase transitions, ClarendonPress, 1992. &amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Modify your statistics() and montecarlostep() functions so that the first N cycles of the simulation are ignored when calculating the averages. You should state in your report what period you chose to ignore, and include graphs from ILfinalframe.py to illustrate your motivation in choosing this figure.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The approach towards implementing a cut-off point was to store the energy and magnetisation states per cycle in self.E_list and self.M_list. This has several advantages. The trajectory of the simulation of every cycle is preserved. These 2 lists can be converted into numpy.arrays() and their means can be obtained using numpy.mean() and standard deviations obtained using numpy.std(). The number of cycles can also be kept track of using len() to observe the length of the lists. There is no need for an &#039;if&#039; condition to determine when to start storing the magnetisation and energy information. Instead, list or array slicing of using a cut-off point &#039;C_Off&#039; can be used. This also bypasses the need to keep track of E2, M2, and number of cycles. The greater use of numpy to calculate means and standard deviations also make statistics calculations faster. &lt;br /&gt;
&lt;br /&gt;
The main downside of such an approach is that the memory used for the calculations would be immense due to keeping track of the energy and magnetisation in a list for every step. For cycles involving millions of steps, the list will contain a million values that consume excess memory.&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = [] # List used instead of E, E2, M, M2&lt;br /&gt;
    M_list = []&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        self.lattice[random_i][random_j] *=  -1 # random spin flipped&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] =  -self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.E_list.append(self.energy())&lt;br /&gt;
        self.M_list.append(self.magnetisation())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
This new statistics() function is called statistics_capacity(self, C_Off) and takes in an argument regarding the cut-off point. This provides flexibility in choosing the cut-off for different equilibration times of different number of spins.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def statistics_capacity(self, C_Off):&lt;br /&gt;
        &amp;quot;Return average values for various properties after a certain cutoff&amp;quot;&lt;br /&gt;
        return [np.mean(self.E_list[C_Off:]),&lt;br /&gt;
                np.std(self.E_list[C_Off:]) ,&lt;br /&gt;
                np.mean(self.M_list[C_Off:]),&lt;br /&gt;
                np.std(self.M_list[C_Off:]),&lt;br /&gt;
                np.mean(np.array(self.E_list[C_Off:])**2),&lt;br /&gt;
                np.mean(np.array(self.M_list[C_Off:])**2)&lt;br /&gt;
                ]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Running over a range of temperatures===&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Use ILtemperaturerange.py to plot the average energy and magnetisation for each temperature, with error bars, for an &amp;lt;math&amp;gt;8\times 8&amp;lt;/math&amp;gt; lattice. Use your intuition and results from the script ILfinalframe.py to estimate how many cycles each simulation should be. The temperature range of of 0.25 to 5.0 is sufficient. Use as many temperature points as you feel necessary to illustrate the trend, but do not use a temperature spacing larger than 0.5. The NumPy function savetxt() stores your array of output data on disk — you will need it later. Save the file as 8x8.dat so that you know which lattice size it came from.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the earlier section, the equilibrium was attained within 100,000 steps for all the various grid sizes and temperatures tested. It would make sense to use that as the standard total number of cycles used. Due to time constraints, larger cycles were not used. In hindsight.&lt;br /&gt;
&lt;br /&gt;
[[File:88_EMT_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for 8X8 particles]]&lt;br /&gt;
&lt;br /&gt;
== The effect of system size ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A. Repeat the final task of the previous section for the following lattice sizes: 2x2, 4x4, 8x8, 16x16, 32x32. Make sure that you name each datafile that your produce after the corresponding lattice size! Write a Python script to make a plot showing the energy &#039;&#039;per spin&#039;&#039; versus temperature for each of your lattice sizes. Hint: the NumPy loadtxt function is the reverse of the savetxt function, and can be used to read your previously saved files into the script. Repeat this for the magnetisation. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for ̈8 x 8 lattice Calculations to generate a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from IsingLattice import *&lt;br /&gt;
&lt;br /&gt;
n_rows = 8&lt;br /&gt;
n_cols = 8&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 = 100000 # set cycle number&lt;br /&gt;
times = range(runtime)&lt;br /&gt;
temps = np.arange(0.25, 5.0, 0.05) # Control the temperature range&lt;br /&gt;
&lt;br /&gt;
energies = [] # Initialise empty lists to store data&lt;br /&gt;
magnetisations = []&lt;br /&gt;
energysq = []&lt;br /&gt;
magnetisationsq = []&lt;br /&gt;
&lt;br /&gt;
for t in temps:    &lt;br /&gt;
    for i in times: # 2 for loops to run data over various temperature and various cycles&lt;br /&gt;
        il.montecarlostep(t)&lt;br /&gt;
        &lt;br /&gt;
    aveE, stdE, aveM, stdM, e2, m2 = il.statistics_capacity(2000) # 2000 arguments represents cut-off point for equilibrium&lt;br /&gt;
    energies.append(aveE)&lt;br /&gt;
    energysq.append(stdE)&lt;br /&gt;
    magnetisations.append(aveM)&lt;br /&gt;
    magnetisationsq.append(stdM)&lt;br /&gt;
    &lt;br /&gt;
    #reset the IL object for the next cycle&lt;br /&gt;
    il.n_cycles = 0&lt;br /&gt;
    il.E_list = []&lt;br /&gt;
    il.M_list  = []&lt;br /&gt;
&lt;br /&gt;
final_data = np.column_stack((temps, energies, energysq, magnetisations, magnetisationsq))&lt;br /&gt;
np.savetxt(&amp;quot;8x8.dat&amp;quot;, final_data)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading a .dat file&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
spins = 64&lt;br /&gt;
&lt;br /&gt;
tempsls, energiesls, energysqls, magnetisationsls, magnetisationsqls = [],[],[],[],[] # initialise empty lists&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;8x8.dat&amp;quot;): # reads data off .dat file &lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines # appends data to empty lists earlier&lt;br /&gt;
    tempsls.append(temps)&lt;br /&gt;
    energiesls.append(energies)&lt;br /&gt;
    energysqls.append(energysq)&lt;br /&gt;
    magnetisationsls.append(magnetisations)&lt;br /&gt;
    magnetisationsqls.append(magnetisationsq)&lt;br /&gt;
&lt;br /&gt;
fig = pl.figure(figsize=&#039;10&#039;)&lt;br /&gt;
&lt;br /&gt;
enerax = fig.add_subplot(2,1,1) # details of the 2 subplots provided&lt;br /&gt;
enerax.set_ylabel(&amp;quot;Energy per spin&amp;quot;)&lt;br /&gt;
enerax.set_xlabel(&amp;quot;Temperature&amp;quot;)&lt;br /&gt;
enerax.set_ylim([-2.1, 0])&lt;br /&gt;
&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;
&lt;br /&gt;
enerax.plot(temps, np.array(energies)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
enerax.errorbar(temps, np.array(energies)/spins,yerr=np.array(energysq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
magax.plot(temps, np.array(magnetisations)/spins,linestyle=&#039;-&#039;,marker=&#039;.&#039;,c=&#039;b&#039;)&lt;br /&gt;
magax.errorbar(temps, np.array(magnetisations)/spins,yerr=np.array(magnetisationsq)/spins,c=&#039;darkorange&#039;,linestyle=&#039;&#039;,markersize=1)&lt;br /&gt;
&lt;br /&gt;
pl.savefig(&#039;8x8.svg&#039;,bbox_inches=&#039;tight&#039;) # save an image&lt;br /&gt;
pl.tight_layout()&lt;br /&gt;
pl.show()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Figsize_results_jmx.png|thumb|center|1000px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. How big a lattice do you think is big enough to capture the long-range fluctuations?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Judging from the standard deviations from the plotted points, the large 16 x 16 and 32 x 32 lattices suffer the least from fluctuations in the cycles.&lt;br /&gt;
&lt;br /&gt;
The bigger the lattice, the larger the sample size involved in an ensemble. The standard deviation is smaller when more samples are considered, as thermal fluctuations tend to cancel out. The impact of a high energy spin-flip at a Monte Carlo perturbation step affects the total energy or magnetisation less when there are more spins involved. The 2 X 2 system experiences high standard deviation when even just 1 spin-flip occurs from the perturbation. This creates massive fluctuations and high standard deviations.&lt;br /&gt;
&lt;br /&gt;
Another major problem introduced by small lattices is the finite-size effects mentioned earlier. These problems result from the periodic boundary conditions of finite-sized lattices. Even though each spin of a lattice site only interacts directly with its edge-sharing neighbours, in reality, the spin of a lattice site influence its neighbours, and the neighbours&#039; spin affects their neighbours and so on and so forth. The correlation length ξ and correlation function Γ measures this. &lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma\left(\widehat{r}_{i}, \widehat{r}_{j}\right)=\left\langle s_{i} s_{j}\right\rangle-\langle s\rangle^{2} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Where &amp;lt;math &amp;gt;widehat{r}_{i}, \widehat{r}_{j} &amp;lt;/math &amp;gt; are the position vectors of the spins of interest, and &amp;lt;math &amp;gt; s_{i} &amp;lt;/math &amp;gt; and &amp;lt;math &amp;gt; s_{j} &amp;lt;/math &amp;gt;are their spins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math &amp;gt; \langle S\rangle &amp;lt;/math &amp;gt; is the average spin per lattice site, or the net lattice magnetisation per spin. Above the critical temperature:&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt; \Gamma \sim \mathrm{r}^{-\tau} \exp (-r / \xi) &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ  is a function of distance. τ is some number. Away from the critical point, the spins are uncorrelated, as their influence on each other becomes increasingly irrelevant. At distances close to each other, the correlation between spins is stronger. The larger the correlation length ξ, the stronger the correlation between spins.&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math &amp;gt;\xi \sim\left|T-T_{C}\right|^{-v} &amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Γ is also a function of temperature. At high temperatures T, Γ decreases to 0, since the spins are randomly distributed. When the temperatures decrease to the critical temperature T_C and the correlation length ξ diverges, increasing to ∞ due to an establishment of magnetic order. &lt;br /&gt;
When the correlation length is small relative to the lattice length, finite-size correction effects are not dominant. However, if the correlation length is comparable to the lattice length, the periodic boundary conditions cause a lattice site spin to interact and influence its own behaviour. For instance, in a 2 x 2 lattice, if the top left site is spin up, it might end up encouraging itself to remain up due to correlation effects. In contrast, in a 32 X 32 system, the top left spin is 32 lattice site spacings away from itself (due to PBC) and experience much weaker long-range correlation effects. At critical temperatures, Γ→∞, so finite-size effects distort behaviour and cause deviations even for large lattice sizes. These fluctuations can be partially compensated for by using larger lattices like 16 x 16 or 32 x32. Their effects are most dominant for smaller lattices like 2 x 2, 4 x 4 and 8 x 8.&lt;br /&gt;
&lt;br /&gt;
== Determining the heat capacity ==&lt;br /&gt;
&lt;br /&gt;
The heat capacity is a metric that aids the identification of the critical point. &lt;br /&gt;
&lt;br /&gt;
==== Task 1====&lt;br /&gt;
&#039;&#039;&#039;By definition,&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\partial \left\langle E\right\rangle}{\partial T}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;From this, show that&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;C = \frac{\mathrm{Var}[E]}{k_B T^2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Proof:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\langle E\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\left\langle E^{2}\right\rangle_{T}=\frac{1}{Z} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
Z=\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above equations are definitions for the expectation values of the energy E and E2 respectively. Z is the partition function.&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1}+\frac{\partial}{\partial T} \frac{1}{Z} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using product rule, the above derivative can be split into the two following components&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial}{\partial T}\left[\sum_{a} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-1} &amp;amp;=-\left[\sum_{\alpha} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-Z^{-2} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Component 2&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\frac{\partial}{\partial T} \sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}=\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Combining the two equations, one can getː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align} \frac{\partial\langle E\rangle_{T}}{\partial T} &amp;amp;=-Z^{-2}\left[\sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{z} \sum_{\alpha} \frac{E(\alpha)}{k_{B} T^{2}} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{1}{Z^{2} k_{B} T^{2}}\left[\sum_{\alpha} E(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\}\right]^{2}+\frac{1}{Z k_{B} T^{2}} \sum_{\alpha} E^{2}(\alpha) \exp \left\{-\frac{E(\alpha)}{k_{B} T}\right\} \\ &amp;amp;=-\frac{\langle E\rangle_{T}^{2}}{k_{B} T^{2}}+\frac{\left\langle E^{2}\right\rangle_{T}}{k_{B} T^{2}} \end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Sample variance S (square of standard deviation σ) is given byː&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\sigma^{2}=\frac{\sum_{a} E_{a}^{2}-\frac{\left(\sum_{a} E_{a}\right)^{2}}{n}}{n}=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For large values of n, &lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; \mathrm{Var}[E]=\left\langle E^{2}\right\rangle_{T}-\langle E\rangle_{T}^{2} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Substituting this into the previous derivative,&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt; C=\frac{\partial\langle E\rangle_{T}}{\partial T}=\frac{\sigma^{2}}{k_{B} T^{2}} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Task 2====&lt;br /&gt;
&#039;&#039;&#039;Write a Python script to make a plot showing the heat capacity versus temperature for each of your lattice sizes from the previous section. You may need to do some research to recall the connection between the variance of a variable, &amp;lt;math&amp;gt;\mathrm{Var}[X]&amp;lt;/math&amp;gt;, the mean of its square &amp;lt;math&amp;gt;\left\langle X^2\right\rangle&amp;lt;/math&amp;gt;, and its squared mean &amp;lt;math&amp;gt;\left\langle X\right\rangle^2&amp;lt;/math&amp;gt;. You may find that the data around the peak is very noisy &amp;amp;mdash; this is normal and is a result of being in the critical region. As before, use the plot controls to save your a PNG image of your plot and attach this to the report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
tempsls32, energiesls32, energysqls32, magnetisationsls32, magnetisationsqls32 = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;32x32.dat&amp;quot;):&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls32.append(temps)&lt;br /&gt;
    energiesls32.append(energies)&lt;br /&gt;
    energysqls32.append(energysq)&lt;br /&gt;
    magnetisationsls32.append(magnetisations)&lt;br /&gt;
    magnetisationsqls32.append(magnetisationsq)&lt;br /&gt;
    &lt;br /&gt;
tempsls32 = np.array(tempsls32)&lt;br /&gt;
energysqls32 = np.array(energysqls32)&lt;br /&gt;
capacity32 = energysqls32**2 / tempsls32**2&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls32,capacity32/32**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
#pl.savefig(&#039;hc3232.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Python_grid_finaljmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
As mentioned earlier, an asymptotic singularity is expected during the phase transition. However, the finite-size effects result in maxima instead of the plots. The critical temperature would also be thrown off from the analytical value of critical temperature. The only way to achieve singularity is to use an infinitely large lattice, which is not computationally feasible and will take forever to equilibrate. The solution to this conundrum will be shown later.&lt;br /&gt;
&lt;br /&gt;
== Locating the Curie temperature ==&lt;br /&gt;
====Task 1====&lt;br /&gt;
&#039;&#039;&#039;A C++ program has been used to run some much longer simulations than would be possible on the college computers in Python. Plot the C++ data against your data. For one lattice size, save a PNG of this comparison and add it to your report — add a legend to the graph to label which is which.&#039;&#039;&#039; &lt;br /&gt;
[[File:Cfit_jmx.png|thumb|center|800px|Plots of Magnetisation, energy with temperature for various number of particles in simulations.]]&lt;br /&gt;
&lt;br /&gt;
===Polynomial fitting===&lt;br /&gt;
====Task 2====&lt;br /&gt;
&#039;&#039;&#039;write a script to read the data from a particular file, and plot C vs T, as well as a fitted polynomial. Try changing the degree of the polynomial to improve the fit — in general, it might be difficult to get a good fit! Attach a PNG of an example fit in your report.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Code for reading .dat file and plotting graph&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
from matplotlib import pylab as pl&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
# Reading file&lt;br /&gt;
tempsls16c, energiesls16c, energysqls16c, magnetisationsls16c, magnetisationsqls16c = [],[],[],[],[] # intialise empty list&lt;br /&gt;
for lines in np.loadtxt(&amp;quot;16x16.dat&amp;quot;): # loop through file&lt;br /&gt;
    temps, energies, energysq, magnetisations, magnetisationsq = lines&lt;br /&gt;
    tempsls16c.append(temps)&lt;br /&gt;
    energiesls16c.append(energies)&lt;br /&gt;
    energysqls16c.append(energysq)&lt;br /&gt;
    magnetisationsls16c.append(magnetisations)&lt;br /&gt;
    magnetisationsqls16c.append(magnetisationsq) # Store data into the empty lists made earlier&lt;br /&gt;
&lt;br /&gt;
tempsls16c = np.array(tempsls16c) # converting lists to numpy arrays for easier handling&lt;br /&gt;
energystd16c = np.array(energysqls16c)-np.array(energiesls16c)**2&lt;br /&gt;
capacity16c = energystd16c / (tempsls16c**2) # Deriving heat capacity from variance of energy and temperature&lt;br /&gt;
&lt;br /&gt;
# Plotting graph&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,capacity16c/16**2, c = &#039;blue&#039;) # find the specific heat capacity per spin&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;) &lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
pl.savefig(&#039;hc1616.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039; Code for fitting and plotting data &#039;&#039;&#039;&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
c16c = capacity16/16**2 # retrive the heat capacity from previously&lt;br /&gt;
&lt;br /&gt;
fit3 = np.polyfit(tempsls16c, c16c, 3)  # polynomial fit of data with polynomials of various orders&lt;br /&gt;
fit4 = np.polyfit(tempsls16c, c16c, 4)&lt;br /&gt;
fit5 = np.polyfit(tempsls16c, c16c, 5)&lt;br /&gt;
fit6 = np.polyfit(tempsls16c, c16c, 6)&lt;br /&gt;
&lt;br /&gt;
fig = plt.figure(figsize=(5,5))&lt;br /&gt;
cap1 = fig.add_subplot(1,1,1)&lt;br /&gt;
cap1.plot(tempsls16c,c16c, c = &#039;darkorange&#039;,marker=&#039;.&#039;,linestyle=&#039;&#039;) # plotting actual data&lt;br /&gt;
&lt;br /&gt;
x3_16c = np.linspace(0.5,5,1000) # generating temperature data to evaluate fit effectiveness&lt;br /&gt;
    &lt;br /&gt;
y3_16c = np.polyval(fit3, x3_16c) # generating heat capacity data to evaluate fit effectiveness&lt;br /&gt;
y4_16c = np.polyval(fit4, x3_16c)&lt;br /&gt;
y5_16c = np.polyval(fit5, x3_16c)&lt;br /&gt;
y6_16c = np.polyval(fit6, x3_16c)&lt;br /&gt;
&lt;br /&gt;
cap1.plot(x3_16c,y3_16c,linestyle=&#039;--&#039;,c=&#039;blue&#039;,label=&#039;3rd Order&#039;) # plotting various fits&lt;br /&gt;
cap1.plot(x3_16c,y4_16c,linestyle=&#039;--&#039;,c=&#039;green&#039;,label=&#039;4th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y5_16c,linestyle=&#039;--&#039;,c=&#039;red&#039;,label=&#039;5th Order&#039;)&lt;br /&gt;
cap1.plot(x3_16c,y6_16c,linestyle=&#039;--&#039;,c=&#039;indigo&#039;,label=&#039;6th Order&#039;)&lt;br /&gt;
&lt;br /&gt;
cap1.set_ylabel(r&amp;quot;Specfic Heat capacity / $k_{b}\ JK^{-1}$ &amp;quot;)&lt;br /&gt;
cap1.set_xlabel(&amp;quot;Temperature / K&amp;quot;)&lt;br /&gt;
cap1.legend()&lt;br /&gt;
plt.savefig(&#039;fitting.svg&#039;,bbox_inches=&#039;tight&#039;)&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:fitting_jmxx.svg|thumb|center|340px|Plot of heat capacity against temperature. Various fits of different polynomial orders are used to attempt to fit the data.]]&lt;br /&gt;
&lt;br /&gt;
Despite increasing the order of the polynomial fit, the plot did not appear to be fit the curves better. Using a polynomial fit does not seem to yield convergence.&lt;br /&gt;
&lt;br /&gt;
====Task 3====&lt;br /&gt;
&#039;&#039;&#039;A. Find the temperature at which the maximum in C occurs for each datafile that you were given.&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
[[File:Finalfrontierjmx.svg|thumb|center|340px|Plots of heat capacity and temperature at various grid sizes. A 6th order polynomial fit was used about the maxima regions]]&lt;br /&gt;
&lt;br /&gt;
The lattice lengths and critical temperatures from the fits are given below. Finite-size effects and correlation lengths cause the critical temperature detected to be different for different lattice sizes. It also causes maxima to be obtained instead of the first-order transition anticipated.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre &amp;gt;&lt;br /&gt;
Lattice length           Critical Temperature&lt;br /&gt;
2.000000000000000000e+00 2.504804804804804608e+00&lt;br /&gt;
4.000000000000000000e+00 2.452902902902903026e+00&lt;br /&gt;
8.000000000000000000e+00 2.338988988988988993e+00&lt;br /&gt;
1.600000000000000000e+01 2.307307307307307376e+00&lt;br /&gt;
3.200000000000000000e+01 2.294344344344344311e+00&lt;br /&gt;
&amp;lt;/pre &amp;gt;&lt;br /&gt;
&lt;br /&gt;
Due to finite size effects, the critical temperature is related to the system size byː&lt;br /&gt;
&lt;br /&gt;
ː&amp;lt;math&amp;gt;T_{C, L} = \frac{A}{L} + T_{C,\infty}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This equation is a good approximation to predict the critical temperature &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;B. Make a plot that uses the scaling relation given above to determine &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt;. By doing a little research online, you should be able to find the theoretical exact Curie temperature for the infinite 2D Ising lattice. How does your value compare to this? Are you surprised by how good/bad the agreement is? Attach a PNG of this final graph to your report, and discuss briefly what you think the major sources of error are in your estimate.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[File:Last_game_jmx.svg|thumb|center|340px|Plot of Curie Temperature against inverse of lattice length to show finite size effects]]&lt;br /&gt;
&lt;br /&gt;
Values for the &amp;lt;math&amp;gt;T_{C,\infty}&amp;lt;/math&amp;gt; obtainedː &amp;lt;math &amp;gt; T_{C, \infty}=2.25 \pm 0.04 &amp;lt;/math &amp;gt; matches the literature value that states Onsager&#039;s analytical value of critical temperature asː&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math &amp;gt;k_{B} T_{c} / J=\frac{2}{\ln (1+\sqrt{2})} \approx 2.26918531421&amp;lt;/math &amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a difference of less than 1%. This is rather impressive. The main sources of error that could have affected these results are a lack of data points and repetition. The data for the c++ code provided largely follow the linear trend, but 16 x 16 lattice seems to deviate significantly from the trend and might have contributed significantly to the error. Also, another inaccuracy comes from only using 5 data points to obtain the linear fit. With more data, the fit could be better. The variance in heat capacity from the c++ code is also unknown since repeats are not performed. With more repeats of the same data points, the results will be more accurate and a weighted linear fit can be used as well. Another minor source of error might originate from the fitting of the function with the numpy polyfit function. Some statistical errors might also come from the noise in stochastic methods, or from variations during the sampling process.&lt;br /&gt;
&lt;br /&gt;
== Extensionsː Hsienberg Model ==&lt;br /&gt;
&#039;&#039;&#039;Code for Hsienberg Model Part 1&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Continuous spin&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
class IsingLattice:&lt;br /&gt;
&lt;br /&gt;
    E_list = []&lt;br /&gt;
    M_list  = []&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, n_rows, n_cols):&lt;br /&gt;
        self.n_rows = n_rows&lt;br /&gt;
        self.n_cols = n_cols&lt;br /&gt;
        self.lattice = np.random.choice(np.arange(0,2*np.pi,0.001), size=(n_rows, n_cols)) # gives a lattice of random angles&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def energy(self):&lt;br /&gt;
        &amp;quot;Return energy by doing a dot product&amp;quot;&lt;br /&gt;
        L = np.cos(np.roll(self.lattice, 1, 1)- self.lattice) # energy is found by taking a cosine of the difference in angles in lattices&lt;br /&gt;
        R = np.cos(np.roll(self.lattice, 1, 0)- self.lattice)&lt;br /&gt;
        return np.sum(-L-R)&lt;br /&gt;
&lt;br /&gt;
    def magnetisation(self):&lt;br /&gt;
        &amp;quot;Return the magnetisation magnitude of the current lattice configuration.&amp;quot;&lt;br /&gt;
        x = np.sum(np.cos(self.lattice)) # the x and y components of the net magnetisation are found&lt;br /&gt;
        y = np.sum(np.sin(self.lattice))&lt;br /&gt;
        complex1 = complex(x,y)&lt;br /&gt;
        return [np.absolute(complex1),np.angle(complex1)] # magnetisation vector made to complex number&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    def montecarlostep(self, T):&lt;br /&gt;
        &amp;quot;Executes Monte Carlo in a Single Step, updates the self quantities&amp;quot;&lt;br /&gt;
        e_old = self.energy()&lt;br /&gt;
        shape = np.copy(self.lattice)&lt;br /&gt;
        random_i = np.random.choice(range(0, self.n_rows)) # choose a random row, column to flip&lt;br /&gt;
        random_j = np.random.choice(range(0, self.n_cols))&lt;br /&gt;
        lattice_old = self.lattice[random_i][random_j]&lt;br /&gt;
&lt;br /&gt;
        self.lattice[random_i][random_j] +=  np.random.random() * 2 * np.pi # random spin flipped by random amount&lt;br /&gt;
        if self.lattice[random_i][random_j] &amp;gt; 2 * np.pi: # periodic conditions, prevent angle from being too big&lt;br /&gt;
            self.lattice[random_i][random_j] -= 2 * np.pi&lt;br /&gt;
        e_new = self.energy()&lt;br /&gt;
        if e_new &amp;gt; e_old:&lt;br /&gt;
            Del_E = e_new - e_old&lt;br /&gt;
            p = np.e ** (-Del_E / T) # Monte Carlo condition&lt;br /&gt;
            random_number = np.random.random() # choose a random number in the range [0,1)&lt;br /&gt;
&lt;br /&gt;
            if random_number &amp;gt; p:&lt;br /&gt;
                self.lattice[random_i][random_j] = lattice_old&lt;br /&gt;
&lt;br /&gt;
        eng = self.energy()&lt;br /&gt;
        mag = self.magnetisation()&lt;br /&gt;
        return [eng,mag,shape] # outputs the energy, magnetisation vector, and lattice shape&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Colouredlattice1jmx.PNG|thumb|center|340px|https://www.youtube.com/watch?v=puknlkcMdq4 &amp;lt;br /&amp;gt;&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Video Illustrating the first 1000 steps of a 8 x 8 Hsienberg model lattice.&lt;/div&gt;</summary>
		<author><name>Mj1516</name></author>
	</entry>
</feed>